mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Compare commits
284 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f84d0a317 | ||
|
|
2f64d42855 | ||
|
|
9c07a8354e | ||
|
|
a7e9bb7618 | ||
|
|
ad118bbbd6 | ||
|
|
d823382732 | ||
|
|
3a5d9129b2 | ||
|
|
17702d1a76 | ||
|
|
9305bfe190 | ||
|
|
0d30a3793a | ||
|
|
a402c485a3 | ||
|
|
05ad2e5008 | ||
|
|
e3bc22f7d5 | ||
|
|
c703dd6056 | ||
|
|
153de3ffef | ||
|
|
3bcc0db4fc | ||
|
|
142403c6cb | ||
|
|
a2d4554c78 | ||
|
|
a89fd2733b | ||
|
|
39bba05a17 | ||
|
|
a8f27f93b7 | ||
|
|
ce3299f106 | ||
|
|
c568923478 | ||
|
|
54eeb8d563 | ||
|
|
e6a0d2884a | ||
|
|
ceabef7e58 | ||
|
|
0be88ab458 | ||
|
|
30756b12ea | ||
|
|
ec354a8a91 | ||
|
|
1a4b22dff8 | ||
|
|
50a26191ea | ||
|
|
a28b02b0ac | ||
|
|
0829bc641e | ||
|
|
b997773139 | ||
|
|
9044f25f33 | ||
|
|
f8bd224c99 | ||
|
|
4d21f1c19c | ||
|
|
5a3e21788d | ||
|
|
816a97ab47 | ||
|
|
d4509bce5f | ||
|
|
ced0e30506 | ||
|
|
2fb543b144 | ||
|
|
b08f6eefe8 | ||
|
|
65d1881f12 | ||
|
|
b000491514 | ||
|
|
70c4585b88 | ||
|
|
aea3713484 | ||
|
|
7f040011f0 | ||
|
|
c6429eae4f | ||
|
|
d2a30d2801 | ||
|
|
a122be7942 | ||
|
|
61f431dff3 | ||
|
|
b19c83578d | ||
|
|
ddfb18b609 | ||
|
|
095434a4c1 | ||
|
|
8a80ced4b8 | ||
|
|
ef2bb6d510 | ||
|
|
889cfdf47e | ||
|
|
c173449c46 | ||
|
|
c08bae8308 | ||
|
|
80f7b084c0 | ||
|
|
5d24d04adf | ||
|
|
7cc9a70e43 | ||
|
|
5ddfb2c2ca | ||
|
|
242c15bf4c | ||
|
|
867f407401 | ||
|
|
54f49f38f2 | ||
|
|
83d2a782f6 | ||
|
|
d6dd8e0d45 | ||
|
|
9029f8ce34 | ||
|
|
ca23b2ed9a | ||
|
|
664f34f2ac | ||
|
|
1c2c404ca9 | ||
|
|
eef4df9063 | ||
|
|
176da44faf | ||
|
|
619d7d1dfe | ||
|
|
2eab4ec19c | ||
|
|
05eff13ec8 | ||
|
|
4d28266eba | ||
|
|
ba9c50c358 | ||
|
|
e1e5dfccc1 | ||
|
|
1097371cf4 | ||
|
|
0b08d6bc52 | ||
|
|
72ed39a481 | ||
|
|
d4683d941f | ||
|
|
f853108d69 | ||
|
|
045ff10826 | ||
|
|
2a746115ca | ||
|
|
37fddc61d8 | ||
|
|
a050250153 | ||
|
|
42e27a360d | ||
|
|
95b9df187f | ||
|
|
7bbe8f467c | ||
|
|
0c67afb6c3 | ||
|
|
68b7c0d38c | ||
|
|
9ba0e25bfe | ||
|
|
9a64347ea6 | ||
|
|
e4e2b804bc | ||
|
|
4533b3e934 | ||
|
|
fd71773668 | ||
|
|
b17d57f737 | ||
|
|
f5d98e3148 | ||
|
|
5f9536af06 | ||
|
|
7c41b31c37 | ||
|
|
a5c43c26f3 | ||
|
|
82aa1480af | ||
|
|
516be406e0 | ||
|
|
1f4840ba2f | ||
|
|
52bc2cb266 | ||
|
|
18502d5250 | ||
|
|
b3f589df62 | ||
|
|
8ebf1279f9 | ||
|
|
19161ae4a0 | ||
|
|
c24e5c63e8 | ||
|
|
e656570d13 | ||
|
|
9e4209b837 | ||
|
|
ed52123206 | ||
|
|
d10bcd3d6c | ||
|
|
45e81a1b0c | ||
|
|
829707fc5a | ||
|
|
a0d862e1f1 | ||
|
|
1dcc135da5 | ||
|
|
95626abdd3 | ||
|
|
152c5422f1 | ||
|
|
98bfdb322a | ||
|
|
d0d99c31b0 | ||
|
|
0e15d6cea8 | ||
|
|
ed03b1aa7f | ||
|
|
0379fb5614 | ||
|
|
fd1263c9aa | ||
|
|
c0bab015a4 | ||
|
|
4428daa411 | ||
|
|
f821fa0f2d | ||
|
|
2dafef1fab | ||
|
|
d83f7639be | ||
|
|
e08efe2598 | ||
|
|
e4ebca0945 | ||
|
|
6bf3ef47e1 | ||
|
|
7deb0a6db9 | ||
|
|
c106638648 | ||
|
|
4dcbbfba5b | ||
|
|
036c4c8e6f | ||
|
|
9ed4526fee | ||
|
|
b16f12faa3 | ||
|
|
9df02aa335 | ||
|
|
d60d3fe1cb | ||
|
|
a554a588c9 | ||
|
|
4a1842c004 | ||
|
|
39ec208171 | ||
|
|
899de8227d | ||
|
|
5af4a16e57 | ||
|
|
fc07de73e3 | ||
|
|
f5ccc1516b | ||
|
|
de0ec1f739 | ||
|
|
b29f238083 | ||
|
|
2941bb9bb8 | ||
|
|
297b44f24b | ||
|
|
429ed5faa5 | ||
|
|
b3029f75cd | ||
|
|
f7bc3aa77c | ||
|
|
cb77d81f8d | ||
|
|
b87617945e | ||
|
|
9289ce8534 | ||
|
|
798d12b499 | ||
|
|
5146760def | ||
|
|
48649d50b5 | ||
|
|
ccd66419f4 | ||
|
|
80334884fb | ||
|
|
4ab45e8c21 | ||
|
|
c566a7abf3 | ||
|
|
fd0048827d | ||
|
|
74960eaeac | ||
|
|
c49102d688 | ||
|
|
40cf8ba2ce | ||
|
|
ffc0ab2d40 | ||
|
|
7183596586 | ||
|
|
5664bfe4b6 | ||
|
|
4074ff4132 | ||
|
|
e7f9885aa3 | ||
|
|
5641e2ac9b | ||
|
|
4cf951596f | ||
|
|
90efcc1ca7 | ||
|
|
8a1c60e54a | ||
|
|
43665a3892 | ||
|
|
640bdbc066 | ||
|
|
c16d9f78b8 | ||
|
|
91f192ce5b | ||
|
|
e560acdac5 | ||
|
|
9d03178b00 | ||
|
|
041fff5057 | ||
|
|
c7f581daad | ||
|
|
b47168994d | ||
|
|
635b25519b | ||
|
|
bc00c30faf | ||
|
|
a28b2a5b4b | ||
|
|
9e611a6148 | ||
|
|
025091c3fb | ||
|
|
b0cede8231 | ||
|
|
22084b26d4 | ||
|
|
867158a942 | ||
|
|
033bab7db1 | ||
|
|
47d9fad45f | ||
|
|
f82dcbea21 | ||
|
|
67f511b5ad | ||
|
|
010f1a4d2d | ||
|
|
0d4b3ed991 | ||
|
|
2f2c8b57e8 | ||
|
|
b952b103e2 | ||
|
|
c85659ebfc | ||
|
|
73c8577b61 | ||
|
|
84c1a20af7 | ||
|
|
28729657ac | ||
|
|
3ebc132c03 | ||
|
|
11a14543c8 | ||
|
|
7e92f04c93 | ||
|
|
8a74b3e259 | ||
|
|
a9fcd584e9 | ||
|
|
a307618872 | ||
|
|
12749088a0 | ||
|
|
90da233341 | ||
|
|
23226dce8f | ||
|
|
9faed1dad0 | ||
|
|
ea2e45d63f | ||
|
|
5abfe8fca9 | ||
|
|
b339e71973 | ||
|
|
a467f036b1 | ||
|
|
cd5ed011a5 | ||
|
|
9dfdaaf471 | ||
|
|
950d1d072f | ||
|
|
cb14e73c61 | ||
|
|
bd2bd79497 | ||
|
|
62272296da | ||
|
|
1b0fcde862 | ||
|
|
46f39efc43 | ||
|
|
b2ea4a7ce5 | ||
|
|
3e307fe062 | ||
|
|
6ee238d961 | ||
|
|
f3e431912d | ||
|
|
471506c5d4 | ||
|
|
2c05221d89 | ||
|
|
a539cd6939 | ||
|
|
11c3974b0f | ||
|
|
605cf2631e | ||
|
|
ee196fd8a3 | ||
|
|
45b7aa797e | ||
|
|
32dab841d7 | ||
|
|
5b1816719f | ||
|
|
bd2270fb05 | ||
|
|
3f40795a98 | ||
|
|
73bc0f5de7 | ||
|
|
f832c0a4ac | ||
|
|
10579c8834 | ||
|
|
f1a6baadc7 | ||
|
|
5de50b9f91 | ||
|
|
cf0d2679aa | ||
|
|
2d388bf8d0 | ||
|
|
056a86fcae | ||
|
|
ebbfd7c56f | ||
|
|
91d98c4413 | ||
|
|
51b6bb210d | ||
|
|
94634a347d | ||
|
|
e5acd27c9b | ||
|
|
e6cd66df53 | ||
|
|
589e7c72ef | ||
|
|
ecbd44df22 | ||
|
|
4ab2ed8b77 | ||
|
|
eb8dabce84 | ||
|
|
c5df302faa | ||
|
|
a581f1ebcd | ||
|
|
78b27ffedb | ||
|
|
e735bdab60 | ||
|
|
5f1efb6f7e | ||
|
|
c68bfedbaa | ||
|
|
871a185ecb | ||
|
|
ed03841fd1 | ||
|
|
d6c0d53442 | ||
|
|
a2a7d94055 | ||
|
|
2a7e452cf8 | ||
|
|
74ae95038c | ||
|
|
57c364fe87 | ||
|
|
04958ece31 | ||
|
|
838bdd711b | ||
|
|
f644db3c79 | ||
|
|
24e7d07973 |
@@ -1,3 +1,9 @@
|
||||
<!--
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
## Azure Pipelines Configuration
|
||||
|
||||
Please see the [Documentation](https://github.com/ansible/community/wiki/Testing:-Azure-Pipelines) for more information.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
@@ -47,6 +52,41 @@ pool: Standard
|
||||
|
||||
stages:
|
||||
### Sanity & units
|
||||
- stage: Ansible_devel
|
||||
displayName: Sanity & Units devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
targets:
|
||||
- name: Sanity
|
||||
test: 'devel/sanity/1'
|
||||
- name: Sanity Extra # Only on devel
|
||||
test: 'devel/sanity/extra'
|
||||
- name: Units
|
||||
test: 'devel/units/1'
|
||||
- stage: Ansible_2_15
|
||||
displayName: Sanity & Units 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
targets:
|
||||
- name: Sanity
|
||||
test: '2.15/sanity/1'
|
||||
- name: Units
|
||||
test: '2.15/units/1'
|
||||
- stage: Ansible_2_14
|
||||
displayName: Sanity & Units 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
targets:
|
||||
- name: Sanity
|
||||
test: '2.14/sanity/1'
|
||||
- name: Units
|
||||
test: '2.14/units/1'
|
||||
- stage: Ansible_2_13
|
||||
displayName: Sanity & Units 2.13
|
||||
dependsOn: []
|
||||
@@ -56,135 +96,267 @@ stages:
|
||||
targets:
|
||||
- name: Sanity
|
||||
test: '2.13/sanity/1'
|
||||
- name: Sanity Extra # Only on devel
|
||||
test: '2.13/sanity/extra'
|
||||
- name: Units
|
||||
test: '2.13/units/1'
|
||||
- stage: Ansible_2_12
|
||||
displayName: Sanity & Units 2.12
|
||||
### Docker
|
||||
- stage: Docker_devel
|
||||
displayName: Docker devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/linux/{0}
|
||||
targets:
|
||||
- name: Sanity
|
||||
test: '2.12/sanity/1'
|
||||
- name: Units
|
||||
test: '2.12/units/1'
|
||||
### Docker
|
||||
- name: Fedora 37
|
||||
test: fedora37
|
||||
- name: openSUSE 15
|
||||
test: opensuse15
|
||||
- name: Ubuntu 20.04
|
||||
test: ubuntu2004
|
||||
- name: Ubuntu 22.04
|
||||
test: ubuntu2204
|
||||
- name: Alpine 3
|
||||
test: alpine3
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Docker_2_15
|
||||
displayName: Docker 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.15/linux/{0}
|
||||
targets:
|
||||
- name: CentOS 7
|
||||
test: centos7
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Docker_2_14
|
||||
displayName: Docker 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.14/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 36
|
||||
test: fedora36
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Docker_2_13
|
||||
displayName: Docker 2.13
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.13/linux/{0}/1
|
||||
testFormat: 2.13/linux/{0}
|
||||
targets:
|
||||
- name: CentOS 7
|
||||
test: centos7
|
||||
- name: Fedora 34
|
||||
test: fedora34
|
||||
- name: Fedora 35
|
||||
test: fedora35
|
||||
- name: openSUSE 15 py2
|
||||
test: opensuse15py2
|
||||
- name: openSUSE 15 py3
|
||||
test: opensuse15
|
||||
- name: Fedora 35
|
||||
test: fedora35
|
||||
- name: Fedora 34
|
||||
test: fedora34
|
||||
- name: Ubuntu 18.04
|
||||
test: ubuntu1804
|
||||
- name: Ubuntu 20.04
|
||||
test: ubuntu2004
|
||||
- stage: Docker_2_12
|
||||
displayName: Docker 2.12
|
||||
- name: Alpine 3
|
||||
test: alpine3
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
|
||||
### Community Docker
|
||||
- stage: Docker_community_devel
|
||||
displayName: Docker (community images) devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.12/linux/{0}/1
|
||||
testFormat: devel/linux-community/{0}
|
||||
targets:
|
||||
- name: CentOS 6
|
||||
test: centos6
|
||||
- name: Fedora 33
|
||||
test: fedora33
|
||||
- name: openSUSE 15 py3
|
||||
test: opensuse15
|
||||
- name: Ubuntu 20.04
|
||||
test: ubuntu2004
|
||||
- name: Debian Bullseye
|
||||
test: debian-bullseye/3.9
|
||||
- name: ArchLinux
|
||||
test: archlinux/3.11
|
||||
- name: CentOS Stream 8 with Python 3.9
|
||||
test: centos-stream8/3.9
|
||||
- name: CentOS Stream 8 with Python 3.6
|
||||
test: centos-stream8/3.6
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
|
||||
### Remote
|
||||
- stage: Remote_devel_extra_vms
|
||||
displayName: Remote devel extra VMs
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: Alpine 3.17
|
||||
test: alpine/3.17
|
||||
- name: Fedora 37
|
||||
test: fedora/37
|
||||
- name: Ubuntu 20.04
|
||||
test: ubuntu/20.04
|
||||
- name: Ubuntu 22.04
|
||||
test: ubuntu/22.04
|
||||
groups:
|
||||
- vm
|
||||
- stage: Remote_devel
|
||||
displayName: Remote devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: macOS 13.2
|
||||
test: macos/13.2
|
||||
- name: RHEL 9.1
|
||||
test: rhel/9.1
|
||||
- name: FreeBSD 12.4
|
||||
test: freebsd/12.4
|
||||
- name: FreeBSD 13.2
|
||||
test: freebsd/13.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Remote_2_15
|
||||
displayName: Remote 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.15/{0}
|
||||
targets:
|
||||
- name: RHEL 7.9
|
||||
test: rhel/7.9
|
||||
- name: FreeBSD 13.1
|
||||
test: freebsd/13.1
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Remote_2_14
|
||||
displayName: Remote 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.14/{0}
|
||||
targets:
|
||||
- name: macOS 12.0
|
||||
test: macos/12.0
|
||||
- name: RHEL 9.0
|
||||
test: rhel/9.0
|
||||
- name: FreeBSD 12.3
|
||||
test: freebsd/12.3
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Remote_2_13
|
||||
displayName: Remote 2.13
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.13/{0}/1
|
||||
testFormat: 2.13/{0}
|
||||
targets:
|
||||
- name: macOS 12.0
|
||||
test: macos/12.0
|
||||
- name: RHEL 7.9
|
||||
test: rhel/7.9
|
||||
- name: RHEL 8.5
|
||||
test: rhel/8.5
|
||||
- name: FreeBSD 12.3
|
||||
test: freebsd/12.3
|
||||
- name: FreeBSD 13.0
|
||||
test: freebsd/13.0
|
||||
- stage: Remote_2_12
|
||||
displayName: Remote 2.12
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.12/{0}/1
|
||||
targets:
|
||||
# - name: macOS 11.1
|
||||
# test: macos/11.1
|
||||
- name: RHEL 8.4
|
||||
test: rhel/8.4
|
||||
- name: FreeBSD 13.0
|
||||
test: freebsd/13.0
|
||||
### cloud
|
||||
- stage: Cloud_2_13
|
||||
displayName: Cloud 2.13
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
### Generic
|
||||
- stage: Generic_devel
|
||||
displayName: Generic devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.13/cloud/{0}/1
|
||||
testFormat: devel/generic/{0}
|
||||
targets:
|
||||
- test: 2.7
|
||||
- test: 3.5
|
||||
- test: 3.6
|
||||
- test: 3.7
|
||||
# - test: 3.8
|
||||
- test: 3.9
|
||||
- test: "3.10"
|
||||
- stage: Cloud_2_12
|
||||
displayName: Cloud 2.12
|
||||
# - test: 3.9
|
||||
# - test: "3.10"
|
||||
- test: "3.11"
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Generic_2_15
|
||||
displayName: Generic 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.12/cloud/{0}/1
|
||||
testFormat: 2.15/generic/{0}
|
||||
targets:
|
||||
- test: 3.5
|
||||
- test: "3.10"
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Generic_2_14
|
||||
displayName: Generic 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.14/generic/{0}
|
||||
targets:
|
||||
- test: 2.6
|
||||
- test: 3.9
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- stage: Generic_2_13
|
||||
displayName: Generic 2.13
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.13/generic/{0}
|
||||
targets:
|
||||
- test: 3.8
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
|
||||
## Finally
|
||||
|
||||
- stage: Summary
|
||||
condition: succeededOrFailed()
|
||||
dependsOn:
|
||||
- Ansible_devel
|
||||
- Ansible_2_15
|
||||
- Ansible_2_14
|
||||
- Ansible_2_13
|
||||
- Ansible_2_12
|
||||
- Remote_devel_extra_vms
|
||||
- Remote_devel
|
||||
- Remote_2_15
|
||||
- Remote_2_14
|
||||
- Remote_2_13
|
||||
- Remote_2_12
|
||||
- Docker_devel
|
||||
- Docker_2_15
|
||||
- Docker_2_14
|
||||
- Docker_2_13
|
||||
- Docker_2_12
|
||||
- Cloud_2_13
|
||||
- Cloud_2_12
|
||||
- Docker_community_devel
|
||||
- Generic_devel
|
||||
- Generic_2_15
|
||||
- Generic_2_14
|
||||
- Generic_2_13
|
||||
jobs:
|
||||
- template: templates/coverage.yml
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Aggregate code coverage results for later processing.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""
|
||||
Combine coverage data from multiple jobs, keeping the data only from the most recent attempt from each job.
|
||||
Coverage artifacts must be named using the format: "Coverage $(System.JobAttempt) {StableUniqueNameForEachJob}"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Check the test results and set variables for use in later steps.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""
|
||||
Upload code coverage reports to codecov.io.
|
||||
Multiple coverage files from multiple languages are accepted and aggregated after upload.
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Generate code coverage reports for uploading to Azure Pipelines and codecov.io.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Configure the test environment and run the tests.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""Prepends a relative timestamp to each input line from stdin and writes it to stdout."""
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This template adds a job for processing code coverage data.
|
||||
# It will upload results to Azure Pipelines and codecov.io.
|
||||
# Use it from a job stage that completes after all other jobs have completed.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This template uses the provided targets and optional groups to generate a matrix which is then passed to the test template.
|
||||
# If this matrix template does not provide the required functionality, consider using the test template directly instead.
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This template uses the provided list of jobs to create test one or more test jobs.
|
||||
# It can be used directly if needed, or through the matrix template.
|
||||
|
||||
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
9
.github/patchback.yml
vendored
Normal file
9
.github/patchback.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
backport_branch_prefix: patchback/backports/
|
||||
backport_label_prefix: backport-
|
||||
target_branch_prefix: stable-
|
||||
...
|
||||
105
.github/workflows/ansible-test.yml
vendored
105
.github/workflows/ansible-test.yml
vendored
@@ -3,13 +3,20 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# For the comprehensive list of the inputs supported by the ansible-community/ansible-test-gh-action GitHub Action, see
|
||||
# https://github.com/marketplace/actions/ansible-test
|
||||
|
||||
name: EOL CI
|
||||
on:
|
||||
# Run EOL CI against all pushes (direct commits, also merged PRs), Pull Requests
|
||||
push:
|
||||
branches:
|
||||
- stable-1
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
# Run EOL CI once per day (at 09:00 UTC)
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
concurrency:
|
||||
# Make sure there is at most one active run per PR, but do not cancel any non-PR runs
|
||||
@@ -25,6 +32,7 @@ jobs:
|
||||
- '2.9'
|
||||
- '2.10'
|
||||
- '2.11'
|
||||
- '2.12'
|
||||
# Ansible-test on various stable branches does not yet work well with cgroups v2.
|
||||
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
|
||||
# image for these stable branches. The list of branches where this is necessary will
|
||||
@@ -38,6 +46,7 @@ jobs:
|
||||
- name: Perform sanity testing
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pull-request-change-detection: 'true'
|
||||
@@ -62,6 +71,7 @@ jobs:
|
||||
- '2.9'
|
||||
- '2.10'
|
||||
- '2.11'
|
||||
- '2.12'
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
@@ -69,6 +79,7 @@ jobs:
|
||||
Ansible version ${{ matrix.ansible }}
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pull-request-change-detection: 'true'
|
||||
@@ -101,67 +112,96 @@ jobs:
|
||||
include:
|
||||
# 2.9
|
||||
- ansible: '2.9'
|
||||
docker: centos6
|
||||
docker: fedora31
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
- ansible: '2.9'
|
||||
docker: centos7
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.9'
|
||||
docker: fedora31
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
- ansible: '2.9'
|
||||
docker: ubuntu1604
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.9'
|
||||
docker: ubuntu1804
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.9'
|
||||
docker: ubuntu1804
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.9'
|
||||
docker: default
|
||||
python: '2.7'
|
||||
target: shippable/cloud/group1/
|
||||
target: azp/generic/1/
|
||||
- ansible: '2.9'
|
||||
docker: default
|
||||
python: '2.7'
|
||||
target: azp/generic/2/
|
||||
# 2.10
|
||||
- ansible: '2.10'
|
||||
docker: centos6
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.10'
|
||||
docker: fedora31
|
||||
docker: centos6
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
- ansible: '2.10'
|
||||
docker: ubuntu1604
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.10'
|
||||
docker: default
|
||||
python: '3.6'
|
||||
target: shippable/cloud/group1/
|
||||
target: azp/generic/1/
|
||||
- ansible: '2.10'
|
||||
docker: default
|
||||
python: '3.6'
|
||||
target: azp/generic/2/
|
||||
# 2.11
|
||||
- ansible: '2.11'
|
||||
docker: centos7
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
- ansible: '2.11'
|
||||
docker: fedora32
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.11'
|
||||
docker: opensuse15py2
|
||||
docker: fedora32
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.11'
|
||||
docker: ubuntu1804
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: shippable/posix/group1/
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.11'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.11'
|
||||
docker: default
|
||||
python: '3.8'
|
||||
target: shippable/cloud/group1/
|
||||
target: azp/generic/1/
|
||||
- ansible: '2.11'
|
||||
docker: default
|
||||
python: '3.8'
|
||||
target: azp/generic/2/
|
||||
# 2.12
|
||||
- ansible: '2.12'
|
||||
docker: centos6
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.12'
|
||||
docker: centos6
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.12'
|
||||
docker: fedora33
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.12'
|
||||
docker: fedora33
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.12'
|
||||
docker: default
|
||||
python: '2.6'
|
||||
target: azp/generic/1/
|
||||
- ansible: '2.12'
|
||||
docker: default
|
||||
python: '3.9'
|
||||
target: azp/generic/2/
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
@@ -170,6 +210,7 @@ jobs:
|
||||
under Python ${{ matrix.python }}
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
docker-image: ${{ matrix.docker }}
|
||||
|
||||
92
.github/workflows/docs-pr.yml
vendored
Normal file
92
.github/workflows/docs-pr.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: Collection Docs
|
||||
concurrency:
|
||||
group: docs-pr-${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
|
||||
env:
|
||||
GHP_BASE_URL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
build-docs:
|
||||
permissions:
|
||||
contents: read
|
||||
name: Build Ansible Docs
|
||||
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-pr.yml@main
|
||||
with:
|
||||
collection-name: community.crypto
|
||||
init-lenient: false
|
||||
init-fail-on-error: true
|
||||
squash-hierarchy: true
|
||||
init-project: Community.Crypto Collection
|
||||
init-copyright: Community.Crypto Contributors
|
||||
init-title: Community.Crypto Collection Documentation
|
||||
init-html-short-title: Community.Crypto Collection Docs
|
||||
init-extra-html-theme-options: |
|
||||
documentation_home_url=https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/branch/main/
|
||||
render-file-line: '> * `$<status>` [$<path_tail>](https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr/${{ github.event.number }}/$<path_tail>)'
|
||||
|
||||
publish-docs-gh-pages:
|
||||
# for now we won't run this on forks
|
||||
if: github.repository == 'ansible-collections/community.crypto'
|
||||
permissions:
|
||||
contents: write
|
||||
needs: [build-docs]
|
||||
name: Publish Ansible Docs
|
||||
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
|
||||
with:
|
||||
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
|
||||
action: ${{ (github.event.action == 'closed' || needs.build-docs.outputs.changed != 'true') && 'teardown' || 'publish' }}
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
comment:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-docs, publish-docs-gh-pages]
|
||||
name: PR comments
|
||||
steps:
|
||||
- name: PR comment
|
||||
uses: ansible-community/github-docs-build/actions/ansible-docs-build-comment@main
|
||||
with:
|
||||
body-includes: '## Docs Build'
|
||||
reactions: heart
|
||||
action: ${{ needs.build-docs.outputs.changed != 'true' && 'remove' || '' }}
|
||||
on-closed-body: |
|
||||
## Docs Build 📝
|
||||
|
||||
This PR is closed and any previously published docsite has been unpublished.
|
||||
on-merged-body: |
|
||||
## Docs Build 📝
|
||||
|
||||
Thank you for contribution!✨
|
||||
|
||||
This PR has been merged and the docs are now incorporated into `main`:
|
||||
${{ env.GHP_BASE_URL }}/branch/main
|
||||
body: |
|
||||
## Docs Build 📝
|
||||
|
||||
Thank you for contribution!✨
|
||||
|
||||
The docs for **this PR** have been published here:
|
||||
${{ env.GHP_BASE_URL }}/pr/${{ github.event.number }}
|
||||
|
||||
You can compare to the docs for the `main` branch here:
|
||||
${{ env.GHP_BASE_URL }}/branch/main
|
||||
|
||||
The docsite for **this PR** is also available for download as an artifact from this run:
|
||||
${{ needs.build-docs.outputs.artifact-url }}
|
||||
|
||||
File changes:
|
||||
|
||||
${{ needs.build-docs.outputs.diff-files-rendered }}
|
||||
|
||||
${{ needs.build-docs.outputs.diff-rendered }}
|
||||
52
.github/workflows/docs-push.yml
vendored
Normal file
52
.github/workflows/docs-push.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: Collection Docs
|
||||
concurrency:
|
||||
group: docs-push-${{ github.sha }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
tags:
|
||||
- '*'
|
||||
# Run CI once per day (at 09:00 UTC)
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
# Allow manual trigger (for newer antsibull-docs, sphinx-ansible-theme, ... versions)
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-docs:
|
||||
permissions:
|
||||
contents: read
|
||||
name: Build Ansible Docs
|
||||
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-push.yml@main
|
||||
with:
|
||||
collection-name: community.crypto
|
||||
init-lenient: false
|
||||
init-fail-on-error: true
|
||||
squash-hierarchy: true
|
||||
init-project: Community.Crypto Collection
|
||||
init-copyright: Community.Crypto Contributors
|
||||
init-title: Community.Crypto Collection Documentation
|
||||
init-html-short-title: Community.Crypto Collection Docs
|
||||
init-extra-html-theme-options: |
|
||||
documentation_home_url=https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/branch/main/
|
||||
|
||||
publish-docs-gh-pages:
|
||||
# for now we won't run this on forks
|
||||
if: github.repository == 'ansible-collections/community.crypto'
|
||||
permissions:
|
||||
contents: write
|
||||
needs: [build-docs]
|
||||
name: Publish Ansible Docs
|
||||
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
|
||||
with:
|
||||
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
185
.github/workflows/ee.yml
vendored
Normal file
185
.github/workflows/ee.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: execution environment
|
||||
on:
|
||||
# Run CI against all pushes (direct commits, also merged PRs), Pull Requests
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
# Run CI once per day (at 04:45 UTC)
|
||||
# This ensures that even if there haven't been commits that we are still testing against latest version of ansible-builder
|
||||
schedule:
|
||||
- cron: '45 4 * * *'
|
||||
|
||||
env:
|
||||
NAMESPACE: community
|
||||
COLLECTION_NAME: crypto
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test EE (${{ matrix.name }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
name:
|
||||
- ''
|
||||
ansible_core:
|
||||
- ''
|
||||
ansible_runner:
|
||||
- ''
|
||||
base_image:
|
||||
- ''
|
||||
pre_base:
|
||||
- ''
|
||||
extra_vars:
|
||||
- ''
|
||||
other_deps:
|
||||
- ''
|
||||
exclude:
|
||||
- ansible_core: ''
|
||||
include:
|
||||
- name: ansible-core devel @ RHEL UBI 9
|
||||
ansible_core: https://github.com/ansible/ansible/archive/devel.tar.gz
|
||||
ansible_runner: ansible-runner
|
||||
base_image: docker.io/redhat/ubi9:latest
|
||||
pre_base: '"#"'
|
||||
# For some reason ansible-builder will not install EPEL dependencies on RHEL
|
||||
extra_vars: -e has_no_pyopenssl=true
|
||||
- name: ansible-core 2.15 @ Rocky Linux 9
|
||||
ansible_core: https://github.com/ansible/ansible/archive/stable-2.15.tar.gz
|
||||
ansible_runner: ansible-runner
|
||||
base_image: quay.io/rockylinux/rockylinux:9
|
||||
pre_base: RUN dnf install -y epel-release
|
||||
# For some reason ansible-builder will not install EPEL dependencies on Rocky Linux
|
||||
extra_vars: -e has_no_pyopenssl=true
|
||||
- name: ansible-core 2.14 @ CentOS Stream 9
|
||||
ansible_core: https://github.com/ansible/ansible/archive/stable-2.14.tar.gz
|
||||
ansible_runner: ansible-runner
|
||||
base_image: quay.io/centos/centos:stream9
|
||||
pre_base: RUN dnf install -y epel-release epel-next-release
|
||||
# For some reason, PyOpenSSL is **broken** on CentOS Stream 9 / EPEL
|
||||
extra_vars: -e has_no_pyopenssl=true
|
||||
- name: ansible-core 2.13 @ RHEL UBI 8
|
||||
ansible_core: https://github.com/ansible/ansible/archive/stable-2.13.tar.gz
|
||||
ansible_runner: ansible-runner
|
||||
other_deps: |2
|
||||
python_interpreter:
|
||||
package_system: python39 python39-pip python39-wheel python39-cryptography
|
||||
base_image: docker.io/redhat/ubi8:latest
|
||||
pre_base: '"#"'
|
||||
# We don't have PyOpenSSL for Python 3.9
|
||||
extra_vars: -e has_no_pyopenssl=true
|
||||
- name: ansible-core 2.12 @ CentOS Stream 8
|
||||
ansible_core: https://github.com/ansible/ansible/archive/stable-2.12.tar.gz
|
||||
ansible_runner: ansible-runner
|
||||
other_deps: |2
|
||||
python_interpreter:
|
||||
package_system: python39 python39-pip python39-wheel python39-cryptography
|
||||
base_image: quay.io/centos/centos:stream8
|
||||
pre_base: '"#"'
|
||||
# We don't have PyOpenSSL for Python 3.9
|
||||
extra_vars: -e has_no_pyopenssl=true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install ansible-builder and ansible-navigator
|
||||
run: pip install ansible-builder ansible-navigator
|
||||
|
||||
- name: Verify requirements
|
||||
run: ansible-builder introspect --sanitize .
|
||||
|
||||
- name: Make sure galaxy.yml has version entry
|
||||
run: >-
|
||||
python -c
|
||||
'import yaml ;
|
||||
f = open("galaxy.yml", "rb") ;
|
||||
data = yaml.safe_load(f) ;
|
||||
f.close() ;
|
||||
data["version"] = data.get("version") or "0.0.1" ;
|
||||
f = open("galaxy.yml", "wb") ;
|
||||
f.write(yaml.dump(data).encode("utf-8")) ;
|
||||
f.close() ;
|
||||
'
|
||||
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
|
||||
|
||||
- name: Build collection
|
||||
run: |
|
||||
ansible-galaxy collection build --output-path ../../../
|
||||
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
|
||||
|
||||
- name: Create files for building execution environment
|
||||
run: |
|
||||
COLLECTION_FILENAME="$(ls "${{ env.NAMESPACE }}-${{ env.COLLECTION_NAME }}"-*.tar.gz)"
|
||||
|
||||
# EE config
|
||||
cat > execution-environment.yml <<EOF
|
||||
---
|
||||
version: 3
|
||||
dependencies:
|
||||
ansible_core:
|
||||
package_pip: ${{ matrix.ansible_core }}
|
||||
ansible_runner:
|
||||
package_pip: ${{ matrix.ansible_runner }}
|
||||
galaxy: requirements.yml
|
||||
${{ matrix.other_deps }}
|
||||
|
||||
images:
|
||||
base_image:
|
||||
name: ${{ matrix.base_image }}
|
||||
|
||||
additional_build_files:
|
||||
- src: ${COLLECTION_FILENAME}
|
||||
dest: src
|
||||
|
||||
additional_build_steps:
|
||||
prepend_base:
|
||||
- ${{ matrix.pre_base }}
|
||||
EOF
|
||||
echo "::group::execution-environment.yml"
|
||||
cat execution-environment.yml
|
||||
echo "::endgroup::"
|
||||
|
||||
# Requirements
|
||||
cat > requirements.yml <<EOF
|
||||
---
|
||||
collections:
|
||||
- name: src/${COLLECTION_FILENAME}
|
||||
type: file
|
||||
EOF
|
||||
echo "::group::requirements.yml"
|
||||
cat requirements.yml
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Build image based on ${{ matrix.base_image }}
|
||||
run: |
|
||||
ansible-builder build --verbosity 3 --tag test-ee:latest --container-runtime podman
|
||||
|
||||
- name: Show images
|
||||
run: podman image ls
|
||||
|
||||
- name: Run basic tests
|
||||
run: >
|
||||
ansible-navigator run
|
||||
--mode stdout
|
||||
--container-engine podman
|
||||
--pull-policy never
|
||||
--set-environment-variable ANSIBLE_PRIVATE_ROLE_VARS=true
|
||||
--execution-environment-image test-ee:latest
|
||||
-v
|
||||
all.yml
|
||||
${{ matrix.extra_vars }}
|
||||
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}/tests/ee
|
||||
34
.github/workflows/reuse.yml
vendored
Normal file
34
.github/workflows/reuse.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: Verify REUSE
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
# Run CI once per day (at 04:45 UTC)
|
||||
schedule:
|
||||
- cron: '45 4 * * *'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install reuse
|
||||
|
||||
- name: Check REUSE compliance (except some PEM files)
|
||||
run: |
|
||||
rm -f tests/integration/targets/*/files/*.pem
|
||||
rm -f tests/integration/targets/*/files/roots/*.pem
|
||||
reuse lint
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Community.crypt specific things
|
||||
/changelogs/.plugin-cache.yaml
|
||||
|
||||
|
||||
5
.reuse/dep5
Normal file
5
.reuse/dep5
Normal file
@@ -0,0 +1,5 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
|
||||
Files: changelogs/fragments/*
|
||||
Copyright: Ansible Project
|
||||
License: GPL-3.0-or-later
|
||||
402
CHANGELOG.rst
402
CHANGELOG.rst
@@ -5,7 +5,22 @@ Community Crypto Release Notes
|
||||
.. contents:: Topics
|
||||
|
||||
|
||||
v1.9.21
|
||||
v2.14.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- acme_certificate - allow to use no challenge by providing ``no challenge`` for the ``challenge`` option. This is needed for ACME servers where validation is done without challenges (https://github.com/ansible-collections/community.crypto/issues/613, https://github.com/ansible-collections/community.crypto/pull/615).
|
||||
- acme_certificate - validate and wait for challenges in parallel instead handling them one after another (https://github.com/ansible-collections/community.crypto/pull/617).
|
||||
- x509_certificate_info - added support for certificates in DER format when using ``path`` parameter (https://github.com/ansible-collections/community.crypto/issues/603).
|
||||
|
||||
v2.13.1
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
@@ -16,50 +31,232 @@ Bugfix release.
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- execution environment definition - fix installation of ``python3-pyOpenSSL`` package on CentOS and RHEL (https://github.com/ansible-collections/community.crypto/pull/606).
|
||||
- execution environment definition - fix source of ``python3-pyOpenSSL`` package for Rocky Linux 9+ (https://github.com/ansible-collections/community.crypto/pull/606).
|
||||
|
||||
v2.13.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix and maintenance release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- x509_crl - the ``crl_mode`` option has been added to replace the existing ``mode`` option (https://github.com/ansible-collections/community.crypto/issues/596).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- x509_crl - the ``mode`` option is deprecated; use ``crl_mode`` instead. The ``mode`` option will change its meaning in community.crypto 3.0.0, and will refer to the CRL file's mode instead (https://github.com/ansible-collections/community.crypto/issues/596).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- openssh_keypair - always generate a new key pair if the private key does not exist. Previously, the module would fail when ``regenerate=fail`` without an existing key, contradicting the documentation (https://github.com/ansible-collections/community.crypto/pull/598).
|
||||
- x509_crl - remove problem with ansible-core 2.16 due to ``AnsibleModule`` is now validating the ``mode`` parameter's values (https://github.com/ansible-collections/community.crypto/issues/596).
|
||||
|
||||
v2.12.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- get_certificate - add ``asn1_base64`` option to control whether the ASN.1 included in the ``extensions`` return value is binary data or Base64 encoded (https://github.com/ansible-collections/community.crypto/pull/592).
|
||||
|
||||
v2.11.1
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Maintenance release with improved documentation.
|
||||
|
||||
v2.11.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Feature and bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- get_certificate - adds ``ciphers`` option for custom cipher selection (https://github.com/ansible-collections/community.crypto/pull/571).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- action plugin helper - fix handling of deprecations for ansible-core 2.14.2 (https://github.com/ansible-collections/community.crypto/pull/572).
|
||||
- openssl_csr, openssl_csr_pipe - prevent invalid values for ``crl_distribution_points`` that do not have one of ``full_name``, ``relative_name``, and ``crl_issuer`` (https://github.com/ansible-collections/community.crypto/pull/560).
|
||||
- execution environment binary dependencies (bindep.txt) - fix ``python3-pyOpenSSL`` dependency resolution on RHEL 9+ / CentOS Stream 9+ platforms (https://github.com/ansible-collections/community.crypto/pull/575).
|
||||
- various plugins - remove unnecessary imports (https://github.com/ansible-collections/community.crypto/pull/569).
|
||||
|
||||
v1.9.20
|
||||
v2.10.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release.
|
||||
Bugfix and feature release.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- openssl_csr, openssl_csr_pipe - prevent invalid values for ``crl_distribution_points`` that do not have one of ``full_name``, ``relative_name``, and ``crl_issuer`` (https://github.com/ansible-collections/community.crypto/pull/560).
|
||||
- openssl_publickey_info - do not crash with internal error when public key cannot be parsed (https://github.com/ansible-collections/community.crypto/pull/551).
|
||||
|
||||
v1.9.19
|
||||
=======
|
||||
New Plugins
|
||||
-----------
|
||||
|
||||
Filter
|
||||
~~~~~~
|
||||
|
||||
- openssl_csr_info - Retrieve information from OpenSSL Certificate Signing Requests (CSR)
|
||||
- openssl_privatekey_info - Retrieve information from OpenSSL private keys
|
||||
- openssl_publickey_info - Retrieve information from OpenSSL public keys in PEM format
|
||||
- split_pem - Split PEM file contents into multiple objects
|
||||
- x509_certificate_info - Retrieve information from X.509 certificates in PEM format
|
||||
- x509_crl_info - Retrieve information from X.509 CRLs in PEM format
|
||||
|
||||
v2.9.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release.
|
||||
Regular feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- x509_certificate_info - adds ``issuer_uri`` field in return value based on Authority Information Access data (https://github.com/ansible-collections/community.crypto/pull/530).
|
||||
|
||||
v2.8.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Maintenance release with improved documentation.
|
||||
|
||||
v2.8.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- acme_* modules - handle more gracefully if CA's new nonce call does not return a nonce (https://github.com/ansible-collections/community.crypto/pull/525).
|
||||
- acme_* modules - include symbolic HTTP status codes in error and log messages when available (https://github.com/ansible-collections/community.crypto/pull/524).
|
||||
- openssl_pkcs12 - add option ``encryption_level`` which allows to chose ``compatibility2022`` when cryptography >= 38.0.0 is used to enable a more backwards compatible encryption algorithm. If cryptography uses OpenSSL 3.0.0 or newer, the default algorithm is not compatible with older software (https://github.com/ansible-collections/community.crypto/pull/523).
|
||||
|
||||
v2.7.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Maintenance release.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- acme_* modules - improve feedback when importing ``cryptography`` does not work (https://github.com/ansible-collections/community.crypto/issues/518, https://github.com/ansible-collections/community.crypto/pull/519).
|
||||
|
||||
v2.7.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- acme* modules - also support the HTTP 503 Service Unavailable and 408 Request Timeout response status for automatic retries (https://github.com/ansible-collections/community.crypto/pull/513).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- openssl_privatekey_pipe - ensure compatibility with newer versions of ansible-core (https://github.com/ansible-collections/community.crypto/pull/515).
|
||||
|
||||
v1.9.18
|
||||
=======
|
||||
v2.6.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release.
|
||||
Feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- acme* modules - support the HTTP 429 Too Many Requests response status (https://github.com/ansible-collections/community.crypto/pull/508).
|
||||
- openssh_keypair - added ``pkcs1``, ``pkcs8``, and ``ssh`` to the available choices for the ``private_key_format`` option (https://github.com/ansible-collections/community.crypto/pull/511).
|
||||
|
||||
v2.5.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Maintenance release with improved licensing declaration and documentation fixes.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- All software licenses are now in the ``LICENSES/`` directory of the collection root. Moreover, ``SPDX-License-Identifier:`` is used to declare the applicable license for every file that is not automatically generated (https://github.com/ansible-collections/community.crypto/pull/491).
|
||||
|
||||
v2.4.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Deprecation and bugfix release. No new features this time.
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- Support for Ansible 2.9 and ansible-base 2.10 is deprecated, and will be removed in the next major release (community.crypto 3.0.0). Some modules might still work with these versions afterwards, but we will no longer keep compatibility code that was needed to support them (https://github.com/ansible-collections/community.crypto/pull/460).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- openssl_pkcs12 - when using the pyOpenSSL backend, do not crash when trying to read non-existing other certificates (https://github.com/ansible-collections/community.crypto/issues/486, https://github.com/ansible-collections/community.crypto/pull/487).
|
||||
|
||||
v1.9.17
|
||||
=======
|
||||
v2.3.4
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Re-release of what was intended to be 2.3.3.
|
||||
|
||||
A mistake during the release process caused the 2.3.3 tag to end up on the
|
||||
commit for 1.9.17, which caused the release pipeline to re-publish 1.9.17
|
||||
as 2.3.3.
|
||||
|
||||
This release is identical to what should have been 2.3.3, except that the
|
||||
version number has been bumped to 2.3.4 and this changelog entry for 2.3.4
|
||||
has been added.
|
||||
|
||||
|
||||
v2.3.3
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
@@ -73,8 +270,8 @@ Bugfixes
|
||||
- openssl_csr - the module no longer crashes with 'permitted_subtrees/excluded_subtrees must be a non-empty list or None' if only one of ``name_constraints_permitted`` and ``name_constraints_excluded`` is provided (https://github.com/ansible-collections/community.crypto/issues/481).
|
||||
- x509_crl - do not crash when signing CRL with Ed25519 or Ed448 keys (https://github.com/ansible-collections/community.crypto/issues/473, https://github.com/ansible-collections/community.crypto/pull/474).
|
||||
|
||||
v1.9.16
|
||||
=======
|
||||
v2.3.2
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
@@ -87,8 +284,8 @@ Bugfixes
|
||||
- Include ``simplified_bsd.txt`` license file for the ECS module utils.
|
||||
- certificate_complete_chain - do not stop execution if an unsupported signature algorithm is encountered; warn instead (https://github.com/ansible-collections/community.crypto/pull/457).
|
||||
|
||||
v1.9.15
|
||||
=======
|
||||
v2.3.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
@@ -100,23 +297,49 @@ Bugfixes
|
||||
|
||||
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
|
||||
|
||||
v1.9.14
|
||||
=======
|
||||
v2.3.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix release.
|
||||
Feature and bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- Prepare collection for inclusion in an Execution Environment by declaring its dependencies. Please note that system packages are used for cryptography and PyOpenSSL, which can be rather limited. If you need features from newer cryptography versions, you will have to manually force a newer version to be installed by pip by specifying something like ``cryptography >= 37.0.0`` in your Execution Environment's Python dependencies file (https://github.com/ansible-collections/community.crypto/pull/440).
|
||||
- Support automatic conversion for Internalionalized Domain Names (IDNs). When passing general names, for example Subject Altenative Names to ``community.crypto.openssl_csr``, these will automatically be converted to IDNA. Conversion will be done per label to IDNA2008 if possible, and IDNA2003 if IDNA2008 conversion fails for that label. Note that IDNA conversion requires `the Python idna library <https://pypi.org/project/idna/>`_ to be installed. Please note that depending on which versions of the cryptography library are used, it could try to process the converted IDNA another time with the Python ``idna`` library and reject IDNA2003 encoded values. Using a new enough ``cryptography`` version avoids this (https://github.com/ansible-collections/community.crypto/issues/426, https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- acme_* modules - add parameter ``request_timeout`` to manage HTTP(S) request timeout (https://github.com/ansible-collections/community.crypto/issues/447, https://github.com/ansible-collections/community.crypto/pull/448).
|
||||
- luks_devices - added ``perf_same_cpu_crypt``, ``perf_submit_from_crypt_cpus``, ``perf_no_read_workqueue``, ``perf_no_write_workqueue`` for performance tuning when opening LUKS2 containers (https://github.com/ansible-collections/community.crypto/issues/427).
|
||||
- luks_devices - added ``persistent`` option when opening LUKS2 containers (https://github.com/ansible-collections/community.crypto/pull/434).
|
||||
- openssl_csr_info - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- openssl_pkcs12 - allow to provide the private key as text instead of having to read it from a file. This allows to store the private key in an encrypted form, for example in Ansible Vault (https://github.com/ansible-collections/community.crypto/pull/452).
|
||||
- x509_certificate_info - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- x509_crl - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- x509_crl_info - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography version (https://github.com/ansible-collections/community.crypto/pull/446).
|
||||
- openssh_* modules - fix exception handling to report traceback to users for enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
|
||||
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography version (https://github.com/ansible-collections/community.crypto/pull/445).
|
||||
- x509_crl - fix crash when ``issuer`` for a revoked certificate is specified (https://github.com/ansible-collections/community.crypto/pull/441).
|
||||
|
||||
v1.9.13
|
||||
=======
|
||||
v2.2.4
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular maintenance release.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- openssh_* modules - fix exception handling to report traceback to users for enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
|
||||
|
||||
v2.2.3
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
@@ -128,32 +351,28 @@ Bugfixes
|
||||
|
||||
- luks_device - fix parsing of ``lsblk`` output when device name ends with ``crypt`` (https://github.com/ansible-collections/community.crypto/issues/409, https://github.com/ansible-collections/community.crypto/pull/410).
|
||||
|
||||
v1.9.12
|
||||
=======
|
||||
v2.2.2
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix release.
|
||||
|
||||
In this release, we extended the test matrix to include Alpine 3, ArchLinux, Debian Bullseye, and CentOS Stream 8. CentOS 8 was removed from the test matrix.
|
||||
|
||||
|
||||
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 - for the ``ownca`` provider, check whether the CA private key actually belongs to the CA certificate (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||
- x509_certificate - regenerate certificate when the CA's public key changes for ``provider=ownca`` (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).
|
||||
- x509_certificate - regenerate certificate when the private key changes for ``provider=selfsigned`` (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
|
||||
=======
|
||||
v2.2.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
@@ -165,22 +384,37 @@ 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
|
||||
=======
|
||||
v2.2.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix release.
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- openssh_cert - added ``ignore_timestamps`` parameter so it can be used semi-idempotent with relative timestamps in ``valid_to``/``valid_from`` (https://github.com/ansible-collections/community.crypto/issues/379).
|
||||
|
||||
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
|
||||
v2.1.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Feature and bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- Adjust error messages that indicate ``cryptography`` is not installed from ``Can't`` to ``Cannot`` (https://github.com/ansible-collections/community.crypto/pull/374).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
@@ -188,7 +422,13 @@ Bugfixes
|
||||
- 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
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- crypto_info - Retrieve cryptographic capabilities
|
||||
- openssl_privatekey_convert - Convert OpenSSL private keys
|
||||
|
||||
v2.0.2
|
||||
======
|
||||
|
||||
Release Summary
|
||||
@@ -196,7 +436,7 @@ Release Summary
|
||||
|
||||
Documentation fix release. No actual code changes.
|
||||
|
||||
v1.9.7
|
||||
v2.0.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
@@ -217,36 +457,80 @@ Bugfixes
|
||||
- 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
|
||||
v2.0.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix release.
|
||||
A new major release of the ``community.crypto`` collection. The main changes are removal of the PyOpenSSL backends for almost all modules (``openssl_pkcs12`` being the only exception), and removal of the ``assertonly`` provider in the ``x509_certificate`` provider. There are also some other breaking changes which should improve the user interface/experience of this collection long-term.
|
||||
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- acme_certificate - the ``subject`` and ``issuer`` fields in in the ``select_chain`` entries are now more strictly validated (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - provide a new ``subject_ordered`` option if the order of the components in the subject is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - there is now stricter validation of the values of the ``subject`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - add ``check_consistency`` option to request private key consistency checks to be done (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_certificate, x509_certificate_pipe - add ``ignore_timestamps`` option which allows to enable idempotency for 'not before' and 'not after' options (https://github.com/ansible-collections/community.crypto/issues/295, https://github.com/ansible-collections/community.crypto/pull/317).
|
||||
- x509_crl - provide a new ``issuer_ordered`` option if the order of the components in the issuer is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- x509_crl - there is now stricter validation of the values of the ``issuer`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
|
||||
Breaking Changes / Porting Guide
|
||||
--------------------------------
|
||||
|
||||
- Adjust ``dirName`` text parsing and to text converting code to conform to `Sections 2 and 3 of RFC 4514 <https://datatracker.ietf.org/doc/html/rfc4514.html>`_. This is similar to how `cryptography handles this <https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Name.rfc4514_string>`_ (https://github.com/ansible-collections/community.crypto/pull/274).
|
||||
- acme module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - removed vendored copy of the Python library ``ipaddress``. If you are using Python 2.x, please make sure to install the library (https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- compatibility module_utils - removed vendored copy of the Python library ``ipaddress`` (https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- crypto module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate, openssl_csr_info, x509_certificate_info - depending on the ``cryptography`` version used, the modules might not return the ASN.1 value for an extension as contained in the certificate respectively CSR, but a re-encoded version of it. This should usually be identical to the value contained in the source file, unless the value was malformed. For extensions not handled by C(cryptography) the value contained in the source file is always returned unaltered (https://github.com/ansible-collections/community.crypto/pull/318).
|
||||
- module_utils - removed various PyOpenSSL support functions and default backend values that are not needed for the openssl_pkcs12 module (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr, openssl_csr_pipe, x509_crl - the ``subject`` respectively ``issuer`` fields no longer ignore empty values, but instead fail when encountering them (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - by default consistency checks are not run; they need to be explicitly requested by passing ``check_consistency=true`` (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_crl - for idempotency checks, the ``issuer`` order is ignored. If order is important, use the new ``issuer_ordered`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- acme_* modules - ACME version 1 is now deprecated and support for it will be removed in community.crypto 2.0.0 (https://github.com/ansible-collections/community.crypto/pull/288).
|
||||
|
||||
Removed Features (previously deprecated)
|
||||
----------------------------------------
|
||||
|
||||
- acme_* modules - the ``acme_directory`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - the ``acme_version`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_facts - the deprecated redirect has been removed. Use community.crypto.acme_account_info instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_info - ``retrieve_orders=url_list`` no longer returns the return value ``orders``. Use the ``order_uris`` return value instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- crypto.info module utils - the deprecated redirect has been removed. Use ``crypto.pem`` instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_certificate - the deprecated redirect has been removed. Use community.crypto.x509_certificate instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_certificate_info - the deprecated redirect has been removed. Use community.crypto.x509_certificate_info instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr and openssl_csr_pipe - ``version`` now only accepts the (default) value 1 (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate - remove ``assertonly`` provider (https://github.com/ansible-collections/community.crypto/pull/289).
|
||||
- x509_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
|
||||
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
|
||||
======
|
||||
|
||||
3
CHANGELOG.rst.license
Normal file
3
CHANGELOG.rst.license
Normal file
@@ -0,0 +1,3 @@
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
SPDX-FileCopyrightText: Ansible Project
|
||||
27
LICENSES/BSD-3-Clause.txt
Normal file
27
LICENSES/BSD-3-Clause.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) Individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of PyCA Cryptography nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1
LICENSES/GPL-3.0-or-later.txt
Symbolic link
1
LICENSES/GPL-3.0-or-later.txt
Symbolic link
@@ -0,0 +1 @@
|
||||
../COPYING
|
||||
27
README.md
27
README.md
@@ -1,6 +1,13 @@
|
||||
<!--
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
# Ansible Community Crypto Collection
|
||||
|
||||
[](https://dev.azure.com/ansible/community.crypto/_build?definitionId=21)
|
||||
[](https://github.com/ansible-collections/community.crypto/actions)
|
||||
[](https://codecov.io/gh/ansible-collections/community.crypto)
|
||||
|
||||
Provides modules for [Ansible](https://www.ansible.com/community) for various cryptographic operations.
|
||||
@@ -11,7 +18,7 @@ Please note that this collection does **not** support Windows targets.
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
Tested with the current Ansible 2.9, ansible-base 2.10, ansible-core 2.11, ansible-core 2.12 and ansible-core 2.13 releases. Ansible versions before 2.9.10 are not supported.
|
||||
Tested with the current Ansible 2.9, ansible-base 2.10, ansible-core 2.11, ansible-core 2.12, ansible-core 2.13, and ansible-core 2.14 releases and the current development version of ansible-core. Ansible versions before 2.9.10 are not supported.
|
||||
|
||||
## External requirements
|
||||
|
||||
@@ -19,6 +26,16 @@ The exact requirements for every module are listed in the module documentation.
|
||||
|
||||
Most modules require a recent enough version of [the Python cryptography library](https://pypi.org/project/cryptography/). See the module documentations for the minimal version supported for each module.
|
||||
|
||||
## Collection Documentation
|
||||
|
||||
Browsing the [**latest** collection documentation](https://docs.ansible.com/ansible/latest/collections/community/crypto) will show docs for the _latest version released in the Ansible package_, not the latest version of the collection released on Galaxy.
|
||||
|
||||
Browsing the [**devel** collection documentation](https://docs.ansible.com/ansible/devel/collections/community/crypto) shows docs for the _latest version released on Galaxy_.
|
||||
|
||||
We also separately publish [**latest commit** collection documentation](https://ansible-collections.github.io/community.crypto/branch/main/) which shows docs for the _latest commit in the `main` branch_.
|
||||
|
||||
If you use the Ansible package and do not update collections independently, use **latest**. If you install or update this collection directly from Galaxy, use **devel**. If you are looking to contribute, use **latest commit**.
|
||||
|
||||
## Included content
|
||||
|
||||
- OpenSSL / PKI modules:
|
||||
@@ -108,6 +125,10 @@ In 2.0.0, the following notable features will be removed:
|
||||
|
||||
## Licensing
|
||||
|
||||
GNU General Public License v3.0 or later.
|
||||
This collection is primarily licensed and distributed as a whole under the GNU General Public License v3.0 or later.
|
||||
|
||||
See [COPYING](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.
|
||||
See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/community.crypto/blob/main/COPYING) for the full text.
|
||||
|
||||
Parts of the collection are licensed under the [Apache 2.0 license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/Apache-2.0.txt) (`plugins/module_utils/crypto/_obj2txt.py` and `plugins/module_utils/crypto/_objects_data.py`), the [BSD 2-Clause license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/BSD-2-Clause.txt) (`plugins/module_utils/ecs/api.py`), the [BSD 3-Clause license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/BSD-3-Clause.txt) (`plugins/module_utils/crypto/_obj2txt.py`), and the [PSF 2.0 license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/PSF-2.0.txt) (`plugins/module_utils/_version.py`). This only applies to vendored files in ``plugins/module_utils/`` and to the ECS module utils.
|
||||
|
||||
Almost all files have a machine readable `SDPX-License-Identifier:` comment denoting its respective license(s) or an equivalent entry in an accompanying `.license` file. Only changelog fragments (which will not be part of a release) are covered by a blanket statement in `.reuse/dep5`. Right now a few vendored PEM files do not have licensing information as well. This conforms to the [REUSE specification](https://reuse.software/spec/) up to the aforementioned PEM files.
|
||||
|
||||
@@ -528,148 +528,6 @@ releases:
|
||||
changes:
|
||||
release_summary: Accidental 1.9.1 release. Identical to 1.9.0.
|
||||
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.13:
|
||||
changes:
|
||||
bugfixes:
|
||||
- luks_device - fix parsing of ``lsblk`` output when device name ends with ``crypt``
|
||||
(https://github.com/ansible-collections/community.crypto/issues/409, https://github.com/ansible-collections/community.crypto/pull/410).
|
||||
release_summary: Regular bugfix release.
|
||||
fragments:
|
||||
- 1.9.13.yml
|
||||
- 410-luks_device-lsblk-parsing.yml
|
||||
release_date: '2022-03-04'
|
||||
1.9.14:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography
|
||||
version (https://github.com/ansible-collections/community.crypto/pull/446).
|
||||
- openssh_* modules - fix exception handling to report traceback to users for
|
||||
enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
|
||||
- x509_crl - fix crash when ``issuer`` for a revoked certificate is specified
|
||||
(https://github.com/ansible-collections/community.crypto/pull/441).
|
||||
release_summary: Regular bugfix release.
|
||||
fragments:
|
||||
- 1.9.14.yml
|
||||
- 417-openssh_modules-fix-exception-reporting.yml
|
||||
- 441-x509-crl-cert-issuer.yml
|
||||
- 446-fix.yml
|
||||
release_date: '2022-05-09'
|
||||
1.9.15:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
|
||||
release_summary: Maintenance release.
|
||||
fragments:
|
||||
- 1.9.15.yml
|
||||
- psf-license.yml
|
||||
release_date: '2022-05-16'
|
||||
1.9.16:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Include ``simplified_bsd.txt`` license file for the ECS module utils.
|
||||
- certificate_complete_chain - do not stop execution if an unsupported signature
|
||||
algorithm is encountered; warn instead (https://github.com/ansible-collections/community.crypto/pull/457).
|
||||
release_summary: Maintenance and bugfix release.
|
||||
fragments:
|
||||
- 1.9.16.yml
|
||||
- 457-certificate_complete_chain-unsupported-algorithm.yml
|
||||
- simplified-bsd-license.yml
|
||||
release_date: '2022-06-02'
|
||||
1.9.17:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Include ``Apache-2.0.txt`` file for ``plugins/module_utils/crypto/_obj2txt.py``
|
||||
and ``plugins/module_utils/crypto/_objects_data.py``.
|
||||
- openssl_csr - the module no longer crashes with 'permitted_subtrees/excluded_subtrees
|
||||
must be a non-empty list or None' if only one of ``name_constraints_permitted``
|
||||
and ``name_constraints_excluded`` is provided (https://github.com/ansible-collections/community.crypto/issues/481).
|
||||
- x509_crl - do not crash when signing CRL with Ed25519 or Ed448 keys (https://github.com/ansible-collections/community.crypto/issues/473,
|
||||
https://github.com/ansible-collections/community.crypto/pull/474).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 1.9.17.yml
|
||||
- 474-x509_crl-ed25519-ed448.yml
|
||||
- 481-fix-excluded_subtrees-must-be-a-non-empty-list-or-None.yml
|
||||
- apache-license.yml
|
||||
release_date: '2022-06-17'
|
||||
1.9.18:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssl_pkcs12 - when using the pyOpenSSL backend, do not crash when trying
|
||||
to read non-existing other certificates (https://github.com/ansible-collections/community.crypto/issues/486,
|
||||
https://github.com/ansible-collections/community.crypto/pull/487).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 1.9.18.yml
|
||||
- 487-openssl_pkcs12-other-certs-crash.yml
|
||||
release_date: '2022-07-09'
|
||||
1.9.19:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssl_privatekey_pipe - ensure compatibility with newer versions of ansible-core
|
||||
(https://github.com/ansible-collections/community.crypto/pull/515).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 1.9.19.yml
|
||||
- 515-action-module-compat.yml
|
||||
release_date: '2022-11-01'
|
||||
1.9.2:
|
||||
changes:
|
||||
release_summary: Bugfix release to fix the changelog. No other change compared
|
||||
@@ -677,30 +535,6 @@ releases:
|
||||
fragments:
|
||||
- 1.9.2.yml
|
||||
release_date: '2021-08-30'
|
||||
1.9.20:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssl_publickey_info - do not crash with internal error when public key
|
||||
cannot be parsed (https://github.com/ansible-collections/community.crypto/pull/551).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 1.9.20.yml
|
||||
- 551-publickey-info.yml
|
||||
release_date: '2023-01-01'
|
||||
1.9.21:
|
||||
changes:
|
||||
bugfixes:
|
||||
- action plugin helper - fix handling of deprecations for ansible-core 2.14.2
|
||||
(https://github.com/ansible-collections/community.crypto/pull/572).
|
||||
- openssl_csr, openssl_csr_pipe - prevent invalid values for ``crl_distribution_points``
|
||||
that do not have one of ``full_name``, ``relative_name``, and ``crl_issuer``
|
||||
(https://github.com/ansible-collections/community.crypto/pull/560).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 1.9.21.yml
|
||||
- 560-openssl_csr-crl_distribution_points.yml
|
||||
- 572-action-module.yml
|
||||
release_date: '2023-04-16'
|
||||
1.9.3:
|
||||
changes:
|
||||
bugfixes:
|
||||
@@ -728,36 +562,119 @@ releases:
|
||||
- 279-acme-openssl.yml
|
||||
- 282-acme_challenge_cert_helper-error.yml
|
||||
release_date: '2021-09-28'
|
||||
1.9.5:
|
||||
2.0.0:
|
||||
changes:
|
||||
breaking_changes:
|
||||
- Adjust ``dirName`` text parsing and to text converting code to conform to
|
||||
`Sections 2 and 3 of RFC 4514 <https://datatracker.ietf.org/doc/html/rfc4514.html>`_.
|
||||
This is similar to how `cryptography handles this <https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Name.rfc4514_string>`_
|
||||
(https://github.com/ansible-collections/community.crypto/pull/274).
|
||||
- acme module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - removed vendored copy of the Python library ``ipaddress``.
|
||||
If you are using Python 2.x, please make sure to install the library (https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- compatibility module_utils - removed vendored copy of the Python library ``ipaddress``
|
||||
(https://github.com/ansible-collections/community.crypto/pull/287).
|
||||
- crypto module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate, openssl_csr_info, x509_certificate_info - depending on the
|
||||
``cryptography`` version used, the modules might not return the ASN.1 value
|
||||
for an extension as contained in the certificate respectively CSR, but a re-encoded
|
||||
version of it. This should usually be identical to the value contained in
|
||||
the source file, unless the value was malformed. For extensions not handled
|
||||
by C(cryptography) the value contained in the source file is always returned
|
||||
unaltered (https://github.com/ansible-collections/community.crypto/pull/318).
|
||||
- module_utils - removed various PyOpenSSL support functions and default backend
|
||||
values that are not needed for the openssl_pkcs12 module (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr, openssl_csr_pipe, x509_crl - the ``subject`` respectively ``issuer``
|
||||
fields no longer ignore empty values, but instead fail when encountering them
|
||||
(https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - by default consistency checks are not run; they
|
||||
need to be explicitly requested by passing ``check_consistency=true`` (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_crl - for idempotency checks, the ``issuer`` order is ignored. If order
|
||||
is important, use the new ``issuer_ordered`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
bugfixes:
|
||||
- cryptography backend - improve Unicode handling for Python 2 (https://github.com/ansible-collections/community.crypto/pull/313).
|
||||
- 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.
|
||||
deprecated_features:
|
||||
- acme_* modules - ACME version 1 is now deprecated and support for it will
|
||||
be removed in community.crypto 2.0.0 (https://github.com/ansible-collections/community.crypto/pull/288).
|
||||
minor_changes:
|
||||
- acme_certificate - the ``subject`` and ``issuer`` fields in in the ``select_chain``
|
||||
entries are now more strictly validated (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - provide a new ``subject_ordered`` option if
|
||||
the order of the components in the subject is of importance (https://github.com/ansible-collections/community.crypto/issues/291,
|
||||
https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_csr, openssl_csr_pipe - there is now stricter validation of the values
|
||||
of the ``subject`` option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- openssl_privatekey_info - add ``check_consistency`` option to request private
|
||||
key consistency checks to be done (https://github.com/ansible-collections/community.crypto/pull/309).
|
||||
- x509_certificate, x509_certificate_pipe - add ``ignore_timestamps`` option
|
||||
which allows to enable idempotency for 'not before' and 'not after' options
|
||||
(https://github.com/ansible-collections/community.crypto/issues/295, https://github.com/ansible-collections/community.crypto/pull/317).
|
||||
- x509_crl - provide a new ``issuer_ordered`` option if the order of the components
|
||||
in the issuer is of importance (https://github.com/ansible-collections/community.crypto/issues/291,
|
||||
https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
- x509_crl - there is now stricter validation of the values of the ``issuer``
|
||||
option (https://github.com/ansible-collections/community.crypto/pull/316).
|
||||
release_summary: 'A new major release of the ``community.crypto`` collection.
|
||||
The main changes are removal of the PyOpenSSL backends for almost all modules
|
||||
(``openssl_pkcs12`` being the only exception), and removal of the ``assertonly``
|
||||
provider in the ``x509_certificate`` provider. There are also some other breaking
|
||||
changes which should improve the user interface/experience of this collection
|
||||
long-term.
|
||||
|
||||
'
|
||||
removed_features:
|
||||
- acme_* modules - the ``acme_directory`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_* modules - the ``acme_version`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_facts - the deprecated redirect has been removed. Use community.crypto.acme_account_info
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- acme_account_info - ``retrieve_orders=url_list`` no longer returns the return
|
||||
value ``orders``. Use the ``order_uris`` return value instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- crypto.info module utils - the deprecated redirect has been removed. Use ``crypto.pem``
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- get_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_certificate - the deprecated redirect has been removed. Use community.crypto.x509_certificate
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_certificate_info - the deprecated redirect has been removed. Use community.crypto.x509_certificate_info
|
||||
instead (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr and openssl_csr_pipe - ``version`` now only accepts the (default)
|
||||
value 1 (https://github.com/ansible-collections/community.crypto/pull/290).
|
||||
- openssl_csr_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_csr_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_privatekey_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_publickey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- openssl_signature_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate - remove ``assertonly`` provider (https://github.com/ansible-collections/community.crypto/pull/289).
|
||||
- x509_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
- x509_certificate_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
|
||||
fragments:
|
||||
- 1.9.5.yml
|
||||
- 2.0.0.yml
|
||||
- 273-pyopenssl-removal.yml
|
||||
- 274-dirname-rfc4514.yml
|
||||
- 287-remove-ipaddress.yml
|
||||
- 288-depecate-acme-v1.yml
|
||||
- 289-assertonly-removed.yml
|
||||
- 290-remove-deprecations.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
|
||||
- 309-openssl_privatekey_info-consistency.yml
|
||||
- 313-unicode-names.yml
|
||||
release_date: '2021-10-30'
|
||||
1.9.7:
|
||||
- 315-ordered-names.yml
|
||||
- 317-ignore-timestamps.yml
|
||||
- 318-extension-value-note.yml
|
||||
release_date: '2021-11-01'
|
||||
2.0.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- acme_certificate - avoid passing multiple certificates to ``cryptography``'s
|
||||
@@ -781,20 +698,20 @@ releases:
|
||||
release_summary: Bugfix release with extra forward compatibility for newer versions
|
||||
of cryptography.
|
||||
fragments:
|
||||
- 1.9.7.yml
|
||||
- 2.0.1.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:
|
||||
2.0.2:
|
||||
changes:
|
||||
release_summary: Documentation fix release. No actual code changes.
|
||||
fragments:
|
||||
- 1.9.8.yml
|
||||
release_date: '2021-12-13'
|
||||
1.9.9:
|
||||
- 2.0.2.yml
|
||||
release_date: '2021-12-20'
|
||||
2.1.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Various modules and plugins - use vendored version of ``distutils.version``
|
||||
@@ -803,7 +720,428 @@ releases:
|
||||
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).
|
||||
minor_changes:
|
||||
- Adjust error messages that indicate ``cryptography`` is not installed from
|
||||
``Can't`` to ``Cannot`` (https://github.com/ansible-collections/community.crypto/pull/374).
|
||||
release_summary: Feature and bugfix release.
|
||||
fragments:
|
||||
- 2.1.0.yml
|
||||
- 353-distutils.version.yml
|
||||
- 360-certificate_complete_chain-loop.yml
|
||||
release_date: '2022-01-11'
|
||||
- 374-docs.yml
|
||||
modules:
|
||||
- description: Retrieve cryptographic capabilities
|
||||
name: crypto_info
|
||||
namespace: ''
|
||||
- description: Convert OpenSSL private keys
|
||||
name: openssl_privatekey_convert
|
||||
namespace: ''
|
||||
release_date: '2022-01-10'
|
||||
2.10.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssl_csr, openssl_csr_pipe - prevent invalid values for ``crl_distribution_points``
|
||||
that do not have one of ``full_name``, ``relative_name``, and ``crl_issuer``
|
||||
(https://github.com/ansible-collections/community.crypto/pull/560).
|
||||
- openssl_publickey_info - do not crash with internal error when public key
|
||||
cannot be parsed (https://github.com/ansible-collections/community.crypto/pull/551).
|
||||
release_summary: Bugfix and feature release.
|
||||
fragments:
|
||||
- 2.10.0.yml
|
||||
- 551-publickey-info.yml
|
||||
- 560-openssl_csr-crl_distribution_points.yml
|
||||
plugins:
|
||||
filter:
|
||||
- description: Retrieve information from OpenSSL Certificate Signing Requests
|
||||
(CSR)
|
||||
name: openssl_csr_info
|
||||
namespace: null
|
||||
- description: Retrieve information from OpenSSL private keys
|
||||
name: openssl_privatekey_info
|
||||
namespace: null
|
||||
- description: Retrieve information from OpenSSL public keys in PEM format
|
||||
name: openssl_publickey_info
|
||||
namespace: null
|
||||
- description: Split PEM file contents into multiple objects
|
||||
name: split_pem
|
||||
namespace: null
|
||||
- description: Retrieve information from X.509 certificates in PEM format
|
||||
name: x509_certificate_info
|
||||
namespace: null
|
||||
- description: Retrieve information from X.509 CRLs in PEM format
|
||||
name: x509_crl_info
|
||||
namespace: null
|
||||
release_date: '2023-01-02'
|
||||
2.11.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- action plugin helper - fix handling of deprecations for ansible-core 2.14.2
|
||||
(https://github.com/ansible-collections/community.crypto/pull/572).
|
||||
- execution environment binary dependencies (bindep.txt) - fix ``python3-pyOpenSSL``
|
||||
dependency resolution on RHEL 9+ / CentOS Stream 9+ platforms (https://github.com/ansible-collections/community.crypto/pull/575).
|
||||
- various plugins - remove unnecessary imports (https://github.com/ansible-collections/community.crypto/pull/569).
|
||||
minor_changes:
|
||||
- get_certificate - adds ``ciphers`` option for custom cipher selection (https://github.com/ansible-collections/community.crypto/pull/571).
|
||||
release_summary: Feature and bugfix release.
|
||||
fragments:
|
||||
- 2.11.0.yml
|
||||
- 571_get_certificate_ciphers.yaml
|
||||
- 572-action-module.yml
|
||||
- 575-bindep-python3-pyOpenSSL.yml
|
||||
- remove-unneeded-imports.yml
|
||||
release_date: '2023-02-23'
|
||||
2.11.1:
|
||||
changes:
|
||||
release_summary: Maintenance release with improved documentation.
|
||||
fragments:
|
||||
- 2.11.1.yml
|
||||
release_date: '2023-03-24'
|
||||
2.12.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- get_certificate - add ``asn1_base64`` option to control whether the ASN.1
|
||||
included in the ``extensions`` return value is binary data or Base64 encoded
|
||||
(https://github.com/ansible-collections/community.crypto/pull/592).
|
||||
release_summary: Feature release.
|
||||
fragments:
|
||||
- 2.12.0.yml
|
||||
- 592-get_certificate-base64.yml
|
||||
release_date: '2023-04-16'
|
||||
2.13.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssh_keypair - always generate a new key pair if the private key does not
|
||||
exist. Previously, the module would fail when ``regenerate=fail`` without
|
||||
an existing key, contradicting the documentation (https://github.com/ansible-collections/community.crypto/pull/598).
|
||||
- x509_crl - remove problem with ansible-core 2.16 due to ``AnsibleModule``
|
||||
is now validating the ``mode`` parameter's values (https://github.com/ansible-collections/community.crypto/issues/596).
|
||||
deprecated_features:
|
||||
- x509_crl - the ``mode`` option is deprecated; use ``crl_mode`` instead. The
|
||||
``mode`` option will change its meaning in community.crypto 3.0.0, and will
|
||||
refer to the CRL file's mode instead (https://github.com/ansible-collections/community.crypto/issues/596).
|
||||
minor_changes:
|
||||
- x509_crl - the ``crl_mode`` option has been added to replace the existing
|
||||
``mode`` option (https://github.com/ansible-collections/community.crypto/issues/596).
|
||||
release_summary: Bugfix and maintenance release.
|
||||
fragments:
|
||||
- 2.13.0.yml
|
||||
- 596-x509_crl-mode.yml
|
||||
- 598-openssh_keypair-generate-new-key.yml
|
||||
release_date: '2023-05-01'
|
||||
2.13.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- execution environment definition - fix installation of ``python3-pyOpenSSL``
|
||||
package on CentOS and RHEL (https://github.com/ansible-collections/community.crypto/pull/606).
|
||||
- execution environment definition - fix source of ``python3-pyOpenSSL`` package
|
||||
for Rocky Linux 9+ (https://github.com/ansible-collections/community.crypto/pull/606).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 2.13.1.yml
|
||||
- 606-ee-rocky.yml
|
||||
release_date: '2023-05-21'
|
||||
2.14.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- acme_certificate - allow to use no challenge by providing ``no challenge``
|
||||
for the ``challenge`` option. This is needed for ACME servers where validation
|
||||
is done without challenges (https://github.com/ansible-collections/community.crypto/issues/613,
|
||||
https://github.com/ansible-collections/community.crypto/pull/615).
|
||||
- acme_certificate - validate and wait for challenges in parallel instead handling
|
||||
them one after another (https://github.com/ansible-collections/community.crypto/pull/617).
|
||||
- x509_certificate_info - added support for certificates in DER format when
|
||||
using ``path`` parameter (https://github.com/ansible-collections/community.crypto/issues/603).
|
||||
release_summary: Feature release.
|
||||
fragments:
|
||||
- 2.14.0.yml
|
||||
- 615-no-challenge.yml
|
||||
- 617-acme_certificate-parallel.yml
|
||||
- 622-der-format-support.yml
|
||||
release_date: '2023-06-15'
|
||||
2.2.0:
|
||||
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).
|
||||
minor_changes:
|
||||
- openssh_cert - added ``ignore_timestamps`` parameter so it can be used semi-idempotent
|
||||
with relative timestamps in ``valid_to``/``valid_from`` (https://github.com/ansible-collections/community.crypto/issues/379).
|
||||
release_summary: Regular bugfix and feature release.
|
||||
fragments:
|
||||
- 2.2.0.yml
|
||||
- 381_openssh_cert_add_ignore_timestamps.yml
|
||||
- 388-luks_device-i18n.yml
|
||||
release_date: '2022-02-01'
|
||||
2.2.1:
|
||||
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:
|
||||
- 2.2.1.yml
|
||||
- 396-openssh_cert-host-cert-idempotence-fix.yml
|
||||
release_date: '2022-02-05'
|
||||
2.2.2:
|
||||
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 (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||
- x509_certificate - regenerate certificate when the CA's public key changes
|
||||
for ``provider=ownca`` (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`` (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||
release_summary: 'Regular bugfix release.
|
||||
|
||||
|
||||
In this release, we extended the test matrix to include Alpine 3, ArchLinux,
|
||||
Debian Bullseye, and CentOS Stream 8. CentOS 8 was removed from the test matrix.
|
||||
|
||||
'
|
||||
fragments:
|
||||
- 2.2.2.yml
|
||||
- 402-x509_certificate-ownca-subject.yml
|
||||
- 403-certificate_complete_chain-same-subject.yml
|
||||
- 407-x509_certificate-signature.yml
|
||||
release_date: '2022-02-21'
|
||||
2.2.3:
|
||||
changes:
|
||||
bugfixes:
|
||||
- luks_device - fix parsing of ``lsblk`` output when device name ends with ``crypt``
|
||||
(https://github.com/ansible-collections/community.crypto/issues/409, https://github.com/ansible-collections/community.crypto/pull/410).
|
||||
release_summary: Regular bugfix release.
|
||||
fragments:
|
||||
- 2.2.3.yml
|
||||
- 410-luks_device-lsblk-parsing.yml
|
||||
release_date: '2022-03-04'
|
||||
2.2.4:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssh_* modules - fix exception handling to report traceback to users for
|
||||
enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
|
||||
release_summary: Regular maintenance release.
|
||||
fragments:
|
||||
- 2.2.4.yml
|
||||
- 417-openssh_modules-fix-exception-reporting.yml
|
||||
release_date: '2022-03-22'
|
||||
2.3.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography
|
||||
version (https://github.com/ansible-collections/community.crypto/pull/445).
|
||||
- x509_crl - fix crash when ``issuer`` for a revoked certificate is specified
|
||||
(https://github.com/ansible-collections/community.crypto/pull/441).
|
||||
minor_changes:
|
||||
- Prepare collection for inclusion in an Execution Environment by declaring
|
||||
its dependencies. Please note that system packages are used for cryptography
|
||||
and PyOpenSSL, which can be rather limited. If you need features from newer
|
||||
cryptography versions, you will have to manually force a newer version to
|
||||
be installed by pip by specifying something like ``cryptography >= 37.0.0``
|
||||
in your Execution Environment's Python dependencies file (https://github.com/ansible-collections/community.crypto/pull/440).
|
||||
- Support automatic conversion for Internalionalized Domain Names (IDNs). When
|
||||
passing general names, for example Subject Altenative Names to ``community.crypto.openssl_csr``,
|
||||
these will automatically be converted to IDNA. Conversion will be done per
|
||||
label to IDNA2008 if possible, and IDNA2003 if IDNA2008 conversion fails for
|
||||
that label. Note that IDNA conversion requires `the Python idna library <https://pypi.org/project/idna/>`_
|
||||
to be installed. Please note that depending on which versions of the cryptography
|
||||
library are used, it could try to process the converted IDNA another time
|
||||
with the Python ``idna`` library and reject IDNA2003 encoded values. Using
|
||||
a new enough ``cryptography`` version avoids this (https://github.com/ansible-collections/community.crypto/issues/426,
|
||||
https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- acme_* modules - add parameter ``request_timeout`` to manage HTTP(S) request
|
||||
timeout (https://github.com/ansible-collections/community.crypto/issues/447,
|
||||
https://github.com/ansible-collections/community.crypto/pull/448).
|
||||
- luks_devices - added ``perf_same_cpu_crypt``, ``perf_submit_from_crypt_cpus``,
|
||||
``perf_no_read_workqueue``, ``perf_no_write_workqueue`` for performance tuning
|
||||
when opening LUKS2 containers (https://github.com/ansible-collections/community.crypto/issues/427).
|
||||
- luks_devices - added ``persistent`` option when opening LUKS2 containers (https://github.com/ansible-collections/community.crypto/pull/434).
|
||||
- openssl_csr_info - add ``name_encoding`` option to control the encoding (IDNA,
|
||||
Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- openssl_pkcs12 - allow to provide the private key as text instead of having
|
||||
to read it from a file. This allows to store the private key in an encrypted
|
||||
form, for example in Ansible Vault (https://github.com/ansible-collections/community.crypto/pull/452).
|
||||
- x509_certificate_info - add ``name_encoding`` option to control the encoding
|
||||
(IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- x509_crl - add ``name_encoding`` option to control the encoding (IDNA, Unicode)
|
||||
used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
- x509_crl_info - add ``name_encoding`` option to control the encoding (IDNA,
|
||||
Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
|
||||
release_summary: Feature and bugfix release.
|
||||
fragments:
|
||||
- 2.3.0.yml
|
||||
- 434-add-persistent-and-perf-options.yml
|
||||
- 436-idns.yml
|
||||
- 440-ee.yml
|
||||
- 441-x509-crl-cert-issuer.yml
|
||||
- 445-fix.yml
|
||||
- 448-acme-request-timeouts.yml
|
||||
- 452-openssl_pkcs12-private-key-content.yml
|
||||
release_date: '2022-05-09'
|
||||
2.3.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
|
||||
release_summary: Maintenance release.
|
||||
fragments:
|
||||
- 2.3.1.yml
|
||||
- psf-license.yml
|
||||
release_date: '2022-05-16'
|
||||
2.3.2:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Include ``simplified_bsd.txt`` license file for the ECS module utils.
|
||||
- certificate_complete_chain - do not stop execution if an unsupported signature
|
||||
algorithm is encountered; warn instead (https://github.com/ansible-collections/community.crypto/pull/457).
|
||||
release_summary: Maintenance and bugfix release.
|
||||
fragments:
|
||||
- 2.3.2.yml
|
||||
- 457-certificate_complete_chain-unsupported-algorithm.yml
|
||||
- simplified-bsd-license.yml
|
||||
release_date: '2022-06-02'
|
||||
2.3.3:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Include ``Apache-2.0.txt`` file for ``plugins/module_utils/crypto/_obj2txt.py``
|
||||
and ``plugins/module_utils/crypto/_objects_data.py``.
|
||||
- openssl_csr - the module no longer crashes with 'permitted_subtrees/excluded_subtrees
|
||||
must be a non-empty list or None' if only one of ``name_constraints_permitted``
|
||||
and ``name_constraints_excluded`` is provided (https://github.com/ansible-collections/community.crypto/issues/481).
|
||||
- x509_crl - do not crash when signing CRL with Ed25519 or Ed448 keys (https://github.com/ansible-collections/community.crypto/issues/473,
|
||||
https://github.com/ansible-collections/community.crypto/pull/474).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 2.3.3.yml
|
||||
- 474-x509_crl-ed25519-ed448.yml
|
||||
- 481-fix-excluded_subtrees-must-be-a-non-empty-list-or-None.yml
|
||||
- apache-license.yml
|
||||
release_date: '2022-06-17'
|
||||
2.3.4:
|
||||
changes:
|
||||
release_summary: 'Re-release of what was intended to be 2.3.3.
|
||||
|
||||
|
||||
A mistake during the release process caused the 2.3.3 tag to end up on the
|
||||
|
||||
commit for 1.9.17, which caused the release pipeline to re-publish 1.9.17
|
||||
|
||||
as 2.3.3.
|
||||
|
||||
|
||||
This release is identical to what should have been 2.3.3, except that the
|
||||
|
||||
version number has been bumped to 2.3.4 and this changelog entry for 2.3.4
|
||||
|
||||
has been added.
|
||||
|
||||
'
|
||||
fragments:
|
||||
- 2.3.4.yml
|
||||
release_date: '2022-06-21'
|
||||
2.4.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssl_pkcs12 - when using the pyOpenSSL backend, do not crash when trying
|
||||
to read non-existing other certificates (https://github.com/ansible-collections/community.crypto/issues/486,
|
||||
https://github.com/ansible-collections/community.crypto/pull/487).
|
||||
deprecated_features:
|
||||
- Support for Ansible 2.9 and ansible-base 2.10 is deprecated, and will be removed
|
||||
in the next major release (community.crypto 3.0.0). Some modules might still
|
||||
work with these versions afterwards, but we will no longer keep compatibility
|
||||
code that was needed to support them (https://github.com/ansible-collections/community.crypto/pull/460).
|
||||
release_summary: Deprecation and bugfix release. No new features this time.
|
||||
fragments:
|
||||
- 2.4.0.yml
|
||||
- 487-openssl_pkcs12-other-certs-crash.yml
|
||||
- deprecate-ansible-2.9-2.10.yml
|
||||
release_date: '2022-07-09'
|
||||
2.5.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- All software licenses are now in the ``LICENSES/`` directory of the collection
|
||||
root. Moreover, ``SPDX-License-Identifier:`` is used to declare the applicable
|
||||
license for every file that is not automatically generated (https://github.com/ansible-collections/community.crypto/pull/491).
|
||||
release_summary: Maintenance release with improved licensing declaration and
|
||||
documentation fixes.
|
||||
fragments:
|
||||
- 2.5.0.yml
|
||||
- 491-licenses.yml
|
||||
release_date: '2022-08-04'
|
||||
2.6.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- acme* modules - support the HTTP 429 Too Many Requests response status (https://github.com/ansible-collections/community.crypto/pull/508).
|
||||
- openssh_keypair - added ``pkcs1``, ``pkcs8``, and ``ssh`` to the available
|
||||
choices for the ``private_key_format`` option (https://github.com/ansible-collections/community.crypto/pull/511).
|
||||
release_summary: Feature release.
|
||||
fragments:
|
||||
- 2.6.0.yml
|
||||
- 508-acme-429.yml
|
||||
- 511-openssh_keypair-private_key_format_options.yml
|
||||
release_date: '2022-09-19'
|
||||
2.7.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openssl_privatekey_pipe - ensure compatibility with newer versions of ansible-core
|
||||
(https://github.com/ansible-collections/community.crypto/pull/515).
|
||||
minor_changes:
|
||||
- acme* modules - also support the HTTP 503 Service Unavailable and 408 Request
|
||||
Timeout response status for automatic retries (https://github.com/ansible-collections/community.crypto/pull/513).
|
||||
release_summary: Feature release.
|
||||
fragments:
|
||||
- 2.7.0.yml
|
||||
- 513-acme-503.yml
|
||||
- 515-action-module-compat.yml
|
||||
release_date: '2022-09-23'
|
||||
2.7.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- acme_* modules - improve feedback when importing ``cryptography`` does not
|
||||
work (https://github.com/ansible-collections/community.crypto/issues/518,
|
||||
https://github.com/ansible-collections/community.crypto/pull/519).
|
||||
release_summary: Maintenance release.
|
||||
fragments:
|
||||
- 2.7.1.yml
|
||||
- 519-acme-cryptography.yml
|
||||
release_date: '2022-10-17'
|
||||
2.8.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- acme_* modules - handle more gracefully if CA's new nonce call does not return
|
||||
a nonce (https://github.com/ansible-collections/community.crypto/pull/525).
|
||||
- acme_* modules - include symbolic HTTP status codes in error and log messages
|
||||
when available (https://github.com/ansible-collections/community.crypto/pull/524).
|
||||
- openssl_pkcs12 - add option ``encryption_level`` which allows to chose ``compatibility2022``
|
||||
when cryptography >= 38.0.0 is used to enable a more backwards compatible
|
||||
encryption algorithm. If cryptography uses OpenSSL 3.0.0 or newer, the default
|
||||
algorithm is not compatible with older software (https://github.com/ansible-collections/community.crypto/pull/523).
|
||||
release_summary: Feature release.
|
||||
fragments:
|
||||
- 2.8.0.yml
|
||||
- 523-pkcs12-compat.yml
|
||||
- 524-acme-http-errors.yml
|
||||
- 525-acme-no-nonce.yml
|
||||
release_date: '2022-11-02'
|
||||
2.8.1:
|
||||
changes:
|
||||
release_summary: Maintenance release with improved documentation.
|
||||
fragments:
|
||||
- 2.8.1.yml
|
||||
release_date: '2022-11-06'
|
||||
2.9.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- x509_certificate_info - adds ``issuer_uri`` field in return value based on
|
||||
Authority Information Access data (https://github.com/ansible-collections/community.crypto/pull/530).
|
||||
release_summary: Regular feature release.
|
||||
fragments:
|
||||
- 2.9.0.yml
|
||||
- aia_issuer.yaml
|
||||
release_date: '2022-11-27'
|
||||
|
||||
3
changelogs/changelog.yaml.license
Normal file
3
changelogs/changelog.yaml.license
Normal file
@@ -0,0 +1,3 @@
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
SPDX-FileCopyrightText: Ansible Project
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
changelog_filename_template: ../CHANGELOG.rst
|
||||
changelog_filename_version_depth: 0
|
||||
changes_file: changelog.yaml
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
sections:
|
||||
- title: Scenario Guides
|
||||
toctree:
|
||||
|
||||
27
docs/docsite/links.yml
Normal file
27
docs/docsite/links.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
edit_on_github:
|
||||
repository: ansible-collections/community.crypto
|
||||
branch: main
|
||||
path_prefix: ''
|
||||
|
||||
extra_links:
|
||||
- description: Submit a bug report
|
||||
url: https://github.com/ansible-collections/community.crypto/issues/new?assignees=&labels=&template=bug_report.md
|
||||
- description: Request a feature
|
||||
url: https://github.com/ansible-collections/community.crypto/issues/new?assignees=&labels=&template=feature_request.md
|
||||
|
||||
communication:
|
||||
matrix_rooms:
|
||||
- topic: General usage and support questions
|
||||
room: '#users:ansible.im'
|
||||
irc_channels:
|
||||
- topic: General usage and support questions
|
||||
network: Libera
|
||||
channel: '#ansible'
|
||||
mailing_lists:
|
||||
- topic: Ansible Project List
|
||||
url: https://groups.google.com/g/ansible-project
|
||||
@@ -1,3 +1,8 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.crypto.docsite.guide_ownca:
|
||||
|
||||
How to create a small CA
|
||||
@@ -29,7 +34,7 @@ The following instructions show how to set up a simple self-signed CA certificat
|
||||
use_common_name_for_san: false # since we do not specify SANs, don't use CN as a SAN
|
||||
basic_constraints:
|
||||
- 'CA:TRUE'
|
||||
basic_constraints_critical: yes
|
||||
basic_constraints_critical: true
|
||||
key_usage:
|
||||
- keyCertSign
|
||||
key_usage_critical: true
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.crypto.docsite.guide_selfsigned:
|
||||
|
||||
How to create self-signed certificates
|
||||
|
||||
15
galaxy.yml
15
galaxy.yml
@@ -1,11 +1,22 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
namespace: community
|
||||
name: crypto
|
||||
version: 1.9.21
|
||||
version: 2.14.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (github.com/ansible)
|
||||
description: null
|
||||
license_file: COPYING
|
||||
license:
|
||||
- GPL-3.0-or-later
|
||||
- Apache-2.0
|
||||
- BSD-2-Clause
|
||||
- BSD-3-Clause
|
||||
- PSF-2.0
|
||||
#license_file: COPYING
|
||||
tags:
|
||||
- acme
|
||||
- certificate
|
||||
|
||||
21
meta/ee-bindep.txt
Normal file
21
meta/ee-bindep.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
cryptsetup [platform:dpkg]
|
||||
cryptsetup [platform:rpm]
|
||||
openssh-client [platform:dpkg]
|
||||
openssh-clients [platform:rpm]
|
||||
openssl [platform:dpkg]
|
||||
openssl [platform:rpm]
|
||||
python3-cryptography [platform:dpkg]
|
||||
python3-cryptography [platform:rpm]
|
||||
python3-openssl [platform:dpkg]
|
||||
# On RHEL 9+, CentOS Stream 9+, and Rocky Linux 9+, python3-pyOpenSSL is part of EPEL
|
||||
python3-pyOpenSSL [platform:rpm !platform:rhel !platform:centos !platform:rocky]
|
||||
python3-pyOpenSSL [platform:rhel-8]
|
||||
python3-pyOpenSSL [platform:rhel !platform:rhel-6 !platform:rhel-7 !platform:rhel-8 epel]
|
||||
python3-pyOpenSSL [platform:centos-8]
|
||||
python3-pyOpenSSL [platform:centos !platform:centos-6 !platform:centos-7 !platform:centos-8 epel]
|
||||
python3-pyOpenSSL [platform:rocky-8]
|
||||
python3-pyOpenSSL [platform:rocky !platform:rocky-8 epel]
|
||||
5
meta/ee-requirements.txt
Normal file
5
meta/ee-requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PyYAML
|
||||
9
meta/execution-environment.yml
Normal file
9
meta/execution-environment.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
version: 1
|
||||
dependencies:
|
||||
python: meta/ee-requirements.txt
|
||||
system: meta/ee-bindep.txt
|
||||
@@ -1,31 +1,34 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
requires_ansible: '>=2.9.10'
|
||||
|
||||
action_groups:
|
||||
acme:
|
||||
- acme_inspect
|
||||
- acme_certificate_revoke
|
||||
- acme_certificate
|
||||
- acme_account
|
||||
- acme_account_facts
|
||||
- acme_account_info
|
||||
- acme_inspect
|
||||
- acme_certificate_revoke
|
||||
- acme_certificate
|
||||
- acme_account
|
||||
- acme_account_info
|
||||
|
||||
plugin_routing:
|
||||
modules:
|
||||
acme_account_facts:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'community.crypto.acme_account_facts' module has been renamed to 'community.crypto.acme_account_info'.
|
||||
openssl_certificate:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'
|
||||
openssl_certificate_info:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'
|
||||
module_utils:
|
||||
crypto.identify:
|
||||
redirect: community.crypto.crypto.pem
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: The 'crypto/identify.py' module_utils has been renamed 'crypto/pem.py'. Please update your imports
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -23,9 +24,17 @@ notes:
|
||||
the L(Let's Encrypt,https://letsencrypt.org/) CA, the module can in
|
||||
principle be used with any CA providing an ACME endpoint, such as
|
||||
L(Buypass Go SSL,https://www.buypass.com/ssl/products/acme)."
|
||||
- "So far, the ACME modules have only been tested by the developers against
|
||||
Let's Encrypt (staging and production), Buypass (staging and production), ZeroSSL (production),
|
||||
and L(Pebble testing server,https://github.com/letsencrypt/Pebble). We have got
|
||||
community feedback that they also work with Sectigo ACME Service for InCommon.
|
||||
If you experience problems with another ACME server, please
|
||||
L(create an issue,https://github.com/ansible-collections/community.crypto/issues/new/choose)
|
||||
to help us supporting it. Feedback that an ACME server not mentioned does work
|
||||
is also appreciated."
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
- either openssl or L(cryptography,https://cryptography.io/) >= 1.5
|
||||
- ipaddress
|
||||
options:
|
||||
account_key_src:
|
||||
description:
|
||||
@@ -33,7 +42,7 @@ options:
|
||||
key."
|
||||
- "Private keys can be created with the
|
||||
M(community.crypto.openssl_privatekey) or M(community.crypto.openssl_privatekey_pipe)
|
||||
modules. If the requisites (pyOpenSSL or cryptography) are not available,
|
||||
modules. If the requisite (cryptography) is not available,
|
||||
keys can also be created directly with the C(openssl) command line tool:
|
||||
RSA keys can be created with C(openssl genrsa ...). Elliptic curve keys
|
||||
can be created with C(openssl ecparam -genkey ...). Any other tool creating
|
||||
@@ -74,9 +83,9 @@ options:
|
||||
- "The ACME version of the endpoint."
|
||||
- "Must be C(1) for the classic Let's Encrypt and Buypass ACME endpoints,
|
||||
or C(2) for standardized ACME v2 endpoints."
|
||||
- "The default value is C(1). Note that in community.crypto 2.0.0, this
|
||||
option B(will be required) and will no longer have a default."
|
||||
- "Please also note that we will deprecate ACME v1 support eventually."
|
||||
- "The value C(1) is deprecated since community.crypto 2.0.0 and will be
|
||||
removed from community.crypto 3.0.0."
|
||||
required: true
|
||||
type: int
|
||||
choices: [ 1, 2 ]
|
||||
acme_directory:
|
||||
@@ -86,41 +95,29 @@ options:
|
||||
- "For safety reasons the default is set to the Let's Encrypt staging
|
||||
server (for the ACME v1 protocol). This will create technically correct,
|
||||
but untrusted certificates."
|
||||
- "The default value is C(https://acme-staging.api.letsencrypt.org/directory).
|
||||
Note that in community.crypto 2.0.0, this option B(will be required) and
|
||||
will no longer have a default. Note that the default is the Let's Encrypt
|
||||
staging server for the ACME v1 protocol, which is deprecated and will
|
||||
be disabled in May 2021 (see
|
||||
L(here,https://community.letsencrypt.org/t/end-of-life-plan-for-acmev1/88430/7)
|
||||
for details)."
|
||||
- "For Let's Encrypt, all staging endpoints can be found here:
|
||||
U(https://letsencrypt.org/docs/staging-environment/). For Buypass, all
|
||||
endpoints can be found here:
|
||||
U(https://community.buypass.com/t/63d4ay/buypass-go-ssl-endpoints)"
|
||||
- "For B(Let's Encrypt), the production directory URL for ACME v2 is
|
||||
U(https://acme-v02.api.letsencrypt.org/directory).
|
||||
(The production directory URL for ACME v1 is
|
||||
U(https://acme-v01.api.letsencrypt.org/directory) and will be
|
||||
disabled in July 2021.)"
|
||||
U(https://acme-v02.api.letsencrypt.org/directory)."
|
||||
- "For B(Buypass), the production directory URL for ACME v2 and v1 is
|
||||
U(https://api.buypass.com/acme/directory)."
|
||||
- "For B(ZeroSSL), the production directory URL for ACME v2 is
|
||||
U(https://acme.zerossl.com/v2/DV90)."
|
||||
- "B(Warning:) So far, the ACME modules have only been tested against Let's Encrypt
|
||||
(staging and production), Buypass (staging and production), ZeroSSL (production),
|
||||
and L(Pebble testing server,https://github.com/letsencrypt/Pebble). If you
|
||||
experience problems with another ACME server, please
|
||||
L(create an issue,https://github.com/ansible-collections/community.crypto/issues/new/choose)
|
||||
to help us supporting it. Feedback that an ACME server not mentioned does work
|
||||
is also appreciated."
|
||||
- "For B(Sectigo), the production directory URL for ACME v2 is
|
||||
U(https://acme-qa.secure.trust-provider.com/v2/DV)."
|
||||
- The notes for this module contain a list of ACME services this module has
|
||||
been tested against.
|
||||
required: true
|
||||
type: str
|
||||
validate_certs:
|
||||
description:
|
||||
- Whether calls to the ACME directory will validate TLS certificates.
|
||||
- "B(Warning:) Should B(only ever) be set to C(no) for testing purposes,
|
||||
- "B(Warning:) Should B(only ever) be set to C(false) for testing purposes,
|
||||
for example when testing against a local Pebble server."
|
||||
type: bool
|
||||
default: yes
|
||||
default: true
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
@@ -132,4 +129,11 @@ options:
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, openssl ]
|
||||
request_timeout:
|
||||
description:
|
||||
- The time Ansible should wait for a response from the ACME API.
|
||||
- This timeout is applied to all HTTP(S) requests (HEAD, GET, POST).
|
||||
type: int
|
||||
default: 10
|
||||
version_added: 2.3.0
|
||||
'''
|
||||
|
||||
85
plugins/doc_fragments/attributes.py
Normal file
85
plugins/doc_fragments/attributes.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
options: {}
|
||||
attributes:
|
||||
check_mode:
|
||||
description: Can run in C(check_mode) and return changed status prediction without modifying target.
|
||||
diff_mode:
|
||||
description: Will return details on what has changed (or possibly needs changing in C(check_mode)), when in diff mode.
|
||||
'''
|
||||
|
||||
# Should be used together with the standard fragment
|
||||
INFO_MODULE = r'''
|
||||
options: {}
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
details:
|
||||
- This action does not modify state.
|
||||
diff_mode:
|
||||
support: N/A
|
||||
details:
|
||||
- This action does not modify state.
|
||||
'''
|
||||
|
||||
ACTIONGROUP_ACME = r'''
|
||||
options: {}
|
||||
attributes:
|
||||
action_group:
|
||||
description: Use C(group/acme) or C(group/community.crypto.acme) in C(module_defaults) to set defaults for this module.
|
||||
support: full
|
||||
membership:
|
||||
- community.crypto.acme
|
||||
- acme
|
||||
'''
|
||||
|
||||
FACTS = r'''
|
||||
options: {}
|
||||
attributes:
|
||||
facts:
|
||||
description: Action returns an C(ansible_facts) dictionary that will update existing host facts.
|
||||
'''
|
||||
|
||||
# Should be used together with the standard fragment and the FACTS fragment
|
||||
FACTS_MODULE = r'''
|
||||
options: {}
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
details:
|
||||
- This action does not modify state.
|
||||
diff_mode:
|
||||
support: N/A
|
||||
details:
|
||||
- This action does not modify state.
|
||||
facts:
|
||||
support: full
|
||||
'''
|
||||
|
||||
FILES = r'''
|
||||
options: {}
|
||||
attributes:
|
||||
safe_file_operations:
|
||||
description: Uses Ansible's strict file operation functions to ensure proper permissions and avoid data corruption.
|
||||
'''
|
||||
|
||||
FLOW = r'''
|
||||
options: {}
|
||||
attributes:
|
||||
action:
|
||||
description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller.
|
||||
async:
|
||||
description: Supports being used with the C(async) keyword.
|
||||
'''
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c), Entrust Datacard Corporation, 2019
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -14,18 +15,15 @@ class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
description:
|
||||
- This module allows one to (re)generate OpenSSL certificates.
|
||||
- It uses the pyOpenSSL or cryptography python library to interact with OpenSSL.
|
||||
- If both the cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
|
||||
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with C(select_crypto_backend)).
|
||||
Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0.
|
||||
- It uses the cryptography python library to interact with OpenSSL.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.6 (if using C(selfsigned), C(ownca) or C(assertonly) provider)
|
||||
- cryptography >= 1.6 (if using C(selfsigned) or C(ownca) provider)
|
||||
options:
|
||||
force:
|
||||
description:
|
||||
- Generate the certificate, even if it already exists.
|
||||
type: bool
|
||||
default: no
|
||||
default: false
|
||||
|
||||
csr_path:
|
||||
description:
|
||||
@@ -55,17 +53,22 @@ options:
|
||||
- This is required if the private key is password protected.
|
||||
type: str
|
||||
|
||||
ignore_timestamps:
|
||||
description:
|
||||
- Whether the "not before" and "not after" timestamps should be ignored for idempotency checks.
|
||||
- It is better to keep the default value C(true) when using relative timestamps (like C(+0s) for now).
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 2.0.0
|
||||
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
choices: [ auto, cryptography ]
|
||||
|
||||
notes:
|
||||
- All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern.
|
||||
@@ -107,9 +110,9 @@ options:
|
||||
- Include the intermediate certificate to the generated certificate
|
||||
- This is only used by the C(acme) provider.
|
||||
- Note that this is only available for older versions of C(acme-tiny).
|
||||
New versions include the chain automatically, and setting I(acme_chain) to C(yes) results in an error.
|
||||
New versions include the chain automatically, and setting I(acme_chain) to C(true) results in an error.
|
||||
type: bool
|
||||
default: no
|
||||
default: false
|
||||
|
||||
acme_directory:
|
||||
description:
|
||||
@@ -119,201 +122,6 @@ options:
|
||||
default: https://acme-v02.api.letsencrypt.org/directory
|
||||
'''
|
||||
|
||||
BACKEND_ASSERTONLY_DOCUMENTATION = r'''
|
||||
description:
|
||||
- The C(assertonly) provider is intended for use cases where one is only interested in
|
||||
checking properties of a supplied certificate. Please note that this provider has been
|
||||
deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. See the examples on how
|
||||
to emulate C(assertonly) usage with M(community.crypto.x509_certificate_info),
|
||||
M(community.crypto.openssl_csr_info), M(community.crypto.openssl_privatekey_info) and
|
||||
M(ansible.builtin.assert). This also allows more flexible checks than
|
||||
the ones offered by the C(assertonly) provider.
|
||||
- Many properties that can be specified in this module are for validation of an
|
||||
existing or newly generated certificate. The proper place to specify them, if you
|
||||
want to receive a certificate with these properties is a CSR (Certificate Signing Request).
|
||||
options:
|
||||
csr_path:
|
||||
description:
|
||||
- This is not required for the C(assertonly) provider.
|
||||
|
||||
csr_content:
|
||||
description:
|
||||
- This is not required for the C(assertonly) provider.
|
||||
|
||||
signature_algorithms:
|
||||
description:
|
||||
- A list of algorithms that you would accept the certificate to be signed with
|
||||
(e.g. ['sha256WithRSAEncryption', 'sha512WithRSAEncryption']).
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
issuer:
|
||||
description:
|
||||
- The key/value pairs that must be present in the issuer name field of the certificate.
|
||||
- If you need to specify more than one value with the same key, use a list as value.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: dict
|
||||
|
||||
issuer_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(issuer) field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
subject:
|
||||
description:
|
||||
- The key/value pairs that must be present in the subject name field of the certificate.
|
||||
- If you need to specify more than one value with the same key, use a list as value.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: dict
|
||||
|
||||
subject_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(subject) field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
has_expired:
|
||||
description:
|
||||
- Checks if the certificate is expired/not expired at the time the module is executed.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
version:
|
||||
description:
|
||||
- The version of the certificate.
|
||||
- Nowadays it should almost always be 3.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: int
|
||||
|
||||
valid_at:
|
||||
description:
|
||||
- The certificate must be valid at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
|
||||
invalid_at:
|
||||
description:
|
||||
- The certificate must be invalid at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
|
||||
not_before:
|
||||
description:
|
||||
- The certificate must start to become valid at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
aliases: [ notBefore ]
|
||||
|
||||
not_after:
|
||||
description:
|
||||
- The certificate must expire at this point in time.
|
||||
- The timestamp is formatted as an ASN.1 TIME.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
aliases: [ notAfter ]
|
||||
|
||||
valid_in:
|
||||
description:
|
||||
- The certificate must still be valid at this relative time offset from now.
|
||||
- Valid format is C([+-]timespec | number_of_seconds) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||
- Note that if using this parameter, this module is NOT idempotent.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: str
|
||||
|
||||
key_usage:
|
||||
description:
|
||||
- The I(key_usage) extension field must contain all these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
aliases: [ keyUsage ]
|
||||
|
||||
key_usage_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(key_usage) extension field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
aliases: [ keyUsage_strict ]
|
||||
|
||||
extended_key_usage:
|
||||
description:
|
||||
- The I(extended_key_usage) extension field must contain all these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
aliases: [ extendedKeyUsage ]
|
||||
|
||||
extended_key_usage_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(extended_key_usage) extension field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
aliases: [ extendedKeyUsage_strict ]
|
||||
|
||||
subject_alt_name:
|
||||
description:
|
||||
- The I(subject_alt_name) extension field must contain these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: list
|
||||
elements: str
|
||||
aliases: [ subjectAltName ]
|
||||
|
||||
subject_alt_name_strict:
|
||||
description:
|
||||
- If set to C(yes), the I(subject_alt_name) extension field must contain only these values.
|
||||
- This is only used by the C(assertonly) provider.
|
||||
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
|
||||
For alternatives, see the example on replacing C(assertonly).
|
||||
type: bool
|
||||
default: no
|
||||
aliases: [ subjectAltName_strict ]
|
||||
'''
|
||||
|
||||
BACKEND_ENTRUST_DOCUMENTATION = r'''
|
||||
options:
|
||||
entrust_cert_type:
|
||||
@@ -386,6 +194,7 @@ options:
|
||||
- The minimum certificate lifetime is 90 days, and maximum is three years.
|
||||
- If this value is not specified, the certificate will stop being valid 365 days the date of issue.
|
||||
- This is only used by the C(entrust) provider.
|
||||
- Please note that this value is B(not) covered by the I(ignore_timestamps) option.
|
||||
type: str
|
||||
default: +365d
|
||||
|
||||
@@ -456,9 +265,11 @@ options:
|
||||
- Time can be specified either as relative time or as absolute timestamp.
|
||||
- Time will always be interpreted as UTC.
|
||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
||||
- 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 can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- This is only used by the C(ownca) provider.
|
||||
type: str
|
||||
default: +0s
|
||||
@@ -469,9 +280,11 @@ options:
|
||||
- Time can be specified either as relative time or as absolute timestamp.
|
||||
- Time will always be interpreted as UTC.
|
||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
||||
- 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 can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- 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.
|
||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||
@@ -501,7 +314,7 @@ options:
|
||||
- This is only used by the C(ownca) provider.
|
||||
- Note that this is only supported if the C(cryptography) backend is used!
|
||||
type: bool
|
||||
default: yes
|
||||
default: true
|
||||
'''
|
||||
|
||||
BACKEND_SELFSIGNED_DOCUMENTATION = r'''
|
||||
@@ -547,9 +360,11 @@ options:
|
||||
- Time can be specified either as relative time or as absolute timestamp.
|
||||
- Time will always be interpreted as UTC.
|
||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
||||
- 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 can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- This is only used by the C(selfsigned) provider.
|
||||
type: str
|
||||
default: +0s
|
||||
@@ -561,9 +376,11 @@ options:
|
||||
- Time can be specified either as relative time or as absolute timestamp.
|
||||
- Time will always be interpreted as UTC.
|
||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
||||
- 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 can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should
|
||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
||||
- 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.
|
||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyrigt: (c) 2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -15,13 +16,8 @@ description:
|
||||
- This module allows one to (re)generate OpenSSL certificate signing requests.
|
||||
- This module supports the subjectAltName, keyUsage, extendedKeyUsage, basicConstraints and OCSP Must Staple
|
||||
extensions.
|
||||
- "The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. Please note that the
|
||||
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
|
||||
requirements:
|
||||
- Either cryptography >= 1.3
|
||||
- Or pyOpenSSL >= 0.15
|
||||
- cryptography >= 1.3
|
||||
options:
|
||||
digest:
|
||||
description:
|
||||
@@ -48,14 +44,29 @@ options:
|
||||
- The version of the certificate signing request.
|
||||
- "The only allowed value according to L(RFC 2986,https://tools.ietf.org/html/rfc2986#section-4.1)
|
||||
is 1."
|
||||
- This option will no longer accept unsupported values from community.crypto 2.0.0 on.
|
||||
- This option no longer accepts unsupported values since community.crypto 2.0.0.
|
||||
type: int
|
||||
default: 1
|
||||
choices:
|
||||
- 1
|
||||
subject:
|
||||
description:
|
||||
- Key/value pairs that will be present in the subject name field of the certificate signing request.
|
||||
- If you need to specify more than one value with the same key, use a list as value.
|
||||
- If the order of the components is important, use I(subject_ordered).
|
||||
- Mutually exclusive with I(subject_ordered).
|
||||
type: dict
|
||||
subject_ordered:
|
||||
description:
|
||||
- A list of dictionaries, where every dictionary must contain one key/value pair. This key/value pair
|
||||
will be present in the subject name field of the certificate signing request.
|
||||
- If you want to specify more than one value with the same key in a row, you can use a list as value.
|
||||
- Mutually exclusive with I(subject), and any other subject field option, such as I(country_name),
|
||||
I(state_or_province_name), I(locality_name), I(organization_name), I(organizational_unit_name),
|
||||
I(common_name), or I(email_address).
|
||||
type: list
|
||||
elements: dict
|
||||
version_added: 2.0.0
|
||||
country_name:
|
||||
description:
|
||||
- The countryName field of the certificate signing request subject.
|
||||
@@ -94,9 +105,8 @@ options:
|
||||
subject_alt_name:
|
||||
description:
|
||||
- Subject Alternative Name (SAN) extension to attach to the certificate signing request.
|
||||
- This can either be a 'comma separated string' or a YAML list.
|
||||
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||
C(otherName) and the ones specific to your CA).
|
||||
- Values must be prefixed by their options. (These are C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||
C(otherName), and the ones specific to your CA).
|
||||
- Note that if no SAN is specified, but a common name, the common
|
||||
name will be added as a SAN except if C(useCommonNameForSAN) is
|
||||
set to I(false).
|
||||
@@ -112,14 +122,14 @@ options:
|
||||
aliases: [ subjectAltName_critical ]
|
||||
use_common_name_for_san:
|
||||
description:
|
||||
- If set to C(yes), the module will fill the common name in for
|
||||
- If set to C(true), the module will fill the common name in for
|
||||
C(subject_alt_name) with C(DNS:) prefix if no SAN is specified.
|
||||
type: bool
|
||||
default: yes
|
||||
default: true
|
||||
aliases: [ useCommonNameForSAN ]
|
||||
key_usage:
|
||||
description:
|
||||
- This defines the purpose (e.g. encipherment, signature, certificate signing)
|
||||
- This defines the purpose (for example encipherment, signature, certificate signing)
|
||||
of the key contained in the certificate.
|
||||
type: list
|
||||
elements: str
|
||||
@@ -132,7 +142,7 @@ options:
|
||||
aliases: [ keyUsage_critical ]
|
||||
extended_key_usage:
|
||||
description:
|
||||
- Additional restrictions (e.g. client authentication, server authentication)
|
||||
- Additional restrictions (for example client authentication, server authentication)
|
||||
on the allowed purposes for which the public key may be used.
|
||||
type: list
|
||||
elements: str
|
||||
@@ -196,14 +206,11 @@ options:
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
choices: [ auto, cryptography ]
|
||||
create_subject_key_identifier:
|
||||
description:
|
||||
- Create the Subject Key Identifier from the public key.
|
||||
@@ -212,7 +219,7 @@ options:
|
||||
certificates or for own CAs."
|
||||
- Note that this is only supported if the C(cryptography) backend is used!
|
||||
type: bool
|
||||
default: no
|
||||
default: false
|
||||
subject_key_identifier:
|
||||
description:
|
||||
- The subject key identifier as a hex string, where two bytes are separated by colons.
|
||||
@@ -220,7 +227,7 @@ options:
|
||||
- "Please note that commercial CAs ignore this value, respectively use a value of their
|
||||
own choice. Specifying this option is mostly useful for self-signed certificates
|
||||
or for own CAs."
|
||||
- Note that this option can only be used if I(create_subject_key_identifier) is C(no).
|
||||
- Note that this option can only be used if I(create_subject_key_identifier) is C(false).
|
||||
- Note that this is only supported if the C(cryptography) backend is used!
|
||||
type: str
|
||||
authority_key_identifier:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -17,18 +18,13 @@ description:
|
||||
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography) or
|
||||
L(EdDSA,https://en.wikipedia.org/wiki/EdDSA) private keys.
|
||||
- Keys are generated in PEM format.
|
||||
- "Please note that the module regenerates private keys if they don't match
|
||||
- "Please note that the module regenerates private keys if they do not match
|
||||
the module's options. In particular, if you provide another passphrase
|
||||
(or specify none), change the keysize, etc., the private key will be
|
||||
regenerated. If you are concerned that this could B(overwrite your private key),
|
||||
consider using the I(backup) option."
|
||||
- "The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. Please note that the
|
||||
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
|
||||
requirements:
|
||||
- Either cryptography >= 1.2.3 (older versions might work as well)
|
||||
- Or pyOpenSSL
|
||||
- cryptography >= 1.2.3 (older versions might work as well)
|
||||
options:
|
||||
size:
|
||||
description:
|
||||
@@ -80,22 +76,16 @@ options:
|
||||
type: str
|
||||
cipher:
|
||||
description:
|
||||
- The cipher to encrypt the private key. (Valid values can be found by
|
||||
running `openssl list -cipher-algorithms` or `openssl list-cipher-algorithms`,
|
||||
depending on your OpenSSL version.)
|
||||
- When using the C(cryptography) backend, use C(auto).
|
||||
- The cipher to encrypt the private key. Must be C(auto).
|
||||
type: str
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
choices: [ auto, cryptography ]
|
||||
format:
|
||||
description:
|
||||
- Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format)
|
||||
@@ -105,8 +95,6 @@ options:
|
||||
selected one for generation.
|
||||
- Note that if the format for an existing private key mismatches, the key is B(regenerated) by default.
|
||||
To change this behavior, use the I(format_mismatch) option.
|
||||
- The I(format) option is only supported by the C(cryptography) backend. The C(pyopenssl) backend will
|
||||
fail if a value different from C(auto_ignore) is used.
|
||||
type: str
|
||||
default: auto_ignore
|
||||
choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ]
|
||||
@@ -124,12 +112,12 @@ options:
|
||||
description:
|
||||
- Allows to configure in which situations the module is allowed to regenerate private keys.
|
||||
The module will always generate a new key if the destination file does not exist.
|
||||
- By default, the key will be regenerated when it doesn't match the module's options,
|
||||
- By default, the key will be regenerated when it does not match the module's options,
|
||||
except when the key cannot be read or the passphrase does not match. Please note that
|
||||
this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if C(full_idempotence)
|
||||
is specified.
|
||||
- If set to C(never), the module will fail if the key cannot be read or the passphrase
|
||||
isn't matching, and will never regenerate an existing key.
|
||||
is not matching, and will never regenerate an existing key.
|
||||
- If set to C(fail), the module will fail if the key does not correspond to the module's
|
||||
options.
|
||||
- If set to C(partial_idempotence), the key will be regenerated if it does not conform to
|
||||
@@ -141,7 +129,7 @@ options:
|
||||
is protected by an unknown passphrase, or when they key is not protected by a passphrase,
|
||||
but a passphrase is specified. Make sure you have a B(backup) when using this option!
|
||||
- If set to C(always), the module will always regenerate the key. This is equivalent to
|
||||
setting I(force) to C(yes).
|
||||
setting I(force) to C(true).
|
||||
- Note that if I(format_mismatch) is set to C(convert) and everything matches except the
|
||||
format, the key will always be converted, except if I(regenerate) is set to C(always).
|
||||
type: str
|
||||
|
||||
48
plugins/doc_fragments/module_privatekey_convert.py
Normal file
48
plugins/doc_fragments/module_privatekey_convert.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard files documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
requirements:
|
||||
- cryptography >= 1.2.3 (older versions might work as well)
|
||||
options:
|
||||
src_path:
|
||||
description:
|
||||
- Name of the file containing the OpenSSL private key to convert.
|
||||
- Exactly one of I(src_path) or I(src_content) must be specified.
|
||||
type: path
|
||||
src_content:
|
||||
description:
|
||||
- The content of the file containing the OpenSSL private key to convert.
|
||||
- Exactly one of I(src_path) or I(src_content) must be specified.
|
||||
type: str
|
||||
src_passphrase:
|
||||
description:
|
||||
- The passphrase for the private key to load.
|
||||
type: str
|
||||
dest_passphrase:
|
||||
description:
|
||||
- The passphrase for the private key to store.
|
||||
type: str
|
||||
format:
|
||||
description:
|
||||
- Determines which format the destination private key should be written in.
|
||||
- Please note that not every key can be exported in any format, and that not every
|
||||
format supports encryption.
|
||||
type: str
|
||||
choices: [ pkcs1, pkcs8, raw ]
|
||||
required: true
|
||||
seealso:
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: community.crypto.openssl_privatekey_pipe
|
||||
- module: community.crypto.openssl_publickey
|
||||
'''
|
||||
31
plugins/doc_fragments/name_encoding.py
Normal file
31
plugins/doc_fragments/name_encoding.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
name_encoding:
|
||||
description:
|
||||
- How to encode names (DNS names, URIs, email addresses) in return values.
|
||||
- C(ignore) will use the encoding returned by the backend.
|
||||
- C(idna) will convert all labels of domain names to IDNA encoding.
|
||||
IDNA2008 will be preferred, and IDNA2003 will be used if IDNA2008 encoding fails.
|
||||
- C(unicode) will convert all labels of domain names to Unicode.
|
||||
IDNA2008 will be preferred, and IDNA2003 will be used if IDNA2008 decoding fails.
|
||||
- B(Note) that C(idna) and C(unicode) require the L(idna Python library,https://pypi.org/project/idna/) to be installed.
|
||||
type: str
|
||||
default: ignore
|
||||
choices:
|
||||
- ignore
|
||||
- idna
|
||||
- unicode
|
||||
requirements:
|
||||
- If I(name_encoding) is set to another value than C(ignore), the L(idna Python library,https://pypi.org/project/idna/) needs to be installed.
|
||||
'''
|
||||
313
plugins/filter/openssl_csr_info.py
Normal file
313
plugins/filter/openssl_csr_info.py
Normal file
@@ -0,0 +1,313 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: openssl_csr_info
|
||||
short_description: Retrieve information from OpenSSL Certificate Signing Requests (CSR)
|
||||
version_added: 2.10.0
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Provided an OpenSSL Certificate Signing Requests (CSR), retrieve information.
|
||||
- This is a filter version of the M(community.crypto.openssl_csr_info) module.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The content of the OpenSSL CSR.
|
||||
type: string
|
||||
required: true
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.name_encoding
|
||||
seealso:
|
||||
- module: community.crypto.openssl_csr_info
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Show the Subject Alt Names of the CSR
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{
|
||||
(
|
||||
lookup('ansible.builtin.file', '/path/to/cert.csr')
|
||||
| community.crypto.openssl_csr_info
|
||||
).subject_alt_name | join(', ')
|
||||
}}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description:
|
||||
- Information on the certificate.
|
||||
type: dict
|
||||
contains:
|
||||
signature_valid:
|
||||
description:
|
||||
- Whether the CSR's signature is valid.
|
||||
- In case the check returns C(false), the module will fail.
|
||||
returned: success
|
||||
type: bool
|
||||
basic_constraints:
|
||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['CA:TRUE', 'pathlen:1']
|
||||
basic_constraints_critical:
|
||||
description: Whether the C(basic_constraints) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extended_key_usage:
|
||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: [Biometric Info, DVCS, Time Stamping]
|
||||
extended_key_usage_critical:
|
||||
description: Whether the C(extended_key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extensions_by_oid:
|
||||
description: Returns a dictionary for every extension OID
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
critical:
|
||||
description: Whether the extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
value:
|
||||
description:
|
||||
- The Base64 encoded value (in DER format) of the extension.
|
||||
- B(Note) that depending on the C(cryptography) version used, it is
|
||||
not possible to extract the ASN.1 content of the extension, but only
|
||||
to provide the re-encoded content of the extension in case it was
|
||||
parsed by C(cryptography). This should usually result in exactly the
|
||||
same value, except if the original extension value was malformed.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "MAMCAQU="
|
||||
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
||||
key_usage:
|
||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: [Key Agreement, Data Encipherment]
|
||||
key_usage_critical:
|
||||
description: Whether the C(key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
subject_alt_name:
|
||||
description:
|
||||
- Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
||||
- See I(name_encoding) for how IDNs are handled.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
|
||||
subject_alt_name_critical:
|
||||
description: Whether the C(subject_alt_name) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple:
|
||||
description: C(true) if the OCSP Must Staple extension is present, C(none) otherwise.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple_critical:
|
||||
description: Whether the C(ocsp_must_staple) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
name_constraints_permitted:
|
||||
description: List of permitted subtrees to sign certificates for.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['email:.somedomain.com']
|
||||
name_constraints_excluded:
|
||||
description:
|
||||
- List of excluded subtrees the CA cannot sign certificates for.
|
||||
- Is C(none) if extension is not present.
|
||||
- See I(name_encoding) for how IDNs are handled.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['email:.com']
|
||||
name_constraints_critical:
|
||||
description:
|
||||
- Whether the C(name_constraints) extension is critical.
|
||||
- Is C(none) if extension is not present.
|
||||
returned: success
|
||||
type: bool
|
||||
subject:
|
||||
description:
|
||||
- The CSR's subject as a dictionary.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: {"commonName": "www.example.com", "emailAddress": "test@example.com"}
|
||||
subject_ordered:
|
||||
description: The CSR's subject as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: [["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]
|
||||
public_key:
|
||||
description: CSR's public key in PEM format
|
||||
returned: success
|
||||
type: str
|
||||
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
|
||||
public_key_type:
|
||||
description:
|
||||
- The CSR's public key's type.
|
||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
||||
- Will start with C(unknown) if the key type cannot be determined.
|
||||
returned: success
|
||||
type: str
|
||||
sample: RSA
|
||||
public_key_data:
|
||||
description:
|
||||
- Public key data. Depends on the public key's type.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
size:
|
||||
description:
|
||||
- Bit size of modulus (RSA) or prime number (DSA).
|
||||
type: int
|
||||
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
|
||||
modulus:
|
||||
description:
|
||||
- The RSA key's modulus.
|
||||
type: int
|
||||
returned: When C(public_key_type=RSA)
|
||||
exponent:
|
||||
description:
|
||||
- The RSA key's public exponent.
|
||||
type: int
|
||||
returned: When C(public_key_type=RSA)
|
||||
p:
|
||||
description:
|
||||
- The C(p) value for DSA.
|
||||
- This is the prime modulus upon which arithmetic takes place.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA)
|
||||
q:
|
||||
description:
|
||||
- The C(q) value for DSA.
|
||||
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
|
||||
multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA)
|
||||
g:
|
||||
description:
|
||||
- The C(g) value for DSA.
|
||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA)
|
||||
curve:
|
||||
description:
|
||||
- The curve's name for ECC.
|
||||
type: str
|
||||
returned: When C(public_key_type=ECC)
|
||||
exponent_size:
|
||||
description:
|
||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||
type: int
|
||||
returned: When C(public_key_type=ECC)
|
||||
x:
|
||||
description:
|
||||
- The C(x) coordinate for the public point on the elliptic curve.
|
||||
type: int
|
||||
returned: When C(public_key_type=ECC)
|
||||
y:
|
||||
description:
|
||||
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
||||
- For C(public_key_type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
|
||||
public_key_fingerprints:
|
||||
description:
|
||||
- Fingerprints of CSR's public key.
|
||||
- For every hash algorithm available, the fingerprint is computed.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
|
||||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
|
||||
subject_key_identifier:
|
||||
description:
|
||||
- The CSR's subject key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
authority_key_identifier:
|
||||
description:
|
||||
- The CSR's authority key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
authority_cert_issuer:
|
||||
description:
|
||||
- The CSR's authority cert issuer as a list of general names.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
- See I(name_encoding) for how IDNs are handled.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
|
||||
authority_cert_serial_number:
|
||||
description:
|
||||
- The CSR's authority cert serial number.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 12345
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.csr_info import (
|
||||
get_csr_info,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
|
||||
|
||||
|
||||
def openssl_csr_info_filter(data, name_encoding='ignore'):
|
||||
'''Extract information from X.509 PEM certificate.'''
|
||||
if not isinstance(data, string_types):
|
||||
raise AnsibleFilterError('The community.crypto.openssl_csr_info input must be a text type, not %s' % type(data))
|
||||
if not isinstance(name_encoding, string_types):
|
||||
raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding))
|
||||
name_encoding = to_native(name_encoding)
|
||||
if name_encoding not in ('ignore', 'idna', 'unicode'):
|
||||
raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding)
|
||||
|
||||
module = FilterModuleMock({'name_encoding': name_encoding})
|
||||
try:
|
||||
return get_csr_info(module, 'cryptography', content=to_bytes(data), validate_signature=True)
|
||||
except OpenSSLObjectError as exc:
|
||||
raise AnsibleFilterError(to_native(exc))
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
'''Ansible jinja2 filters'''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'openssl_csr_info': openssl_csr_info_filter,
|
||||
}
|
||||
193
plugins/filter/openssl_privatekey_info.py
Normal file
193
plugins/filter/openssl_privatekey_info.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: openssl_privatekey_info
|
||||
short_description: Retrieve information from OpenSSL private keys
|
||||
version_added: 2.10.0
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Provided an OpenSSL private keys, retrieve information.
|
||||
- This is a filter version of the M(community.crypto.openssl_privatekey_info) module.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The content of the OpenSSL private key.
|
||||
type: string
|
||||
required: true
|
||||
passphrase:
|
||||
description:
|
||||
- The passphrase for the private key.
|
||||
type: str
|
||||
return_private_key_data:
|
||||
description:
|
||||
- Whether to return private key data.
|
||||
- Only set this to C(true) when you want private information about this key to
|
||||
be extracted.
|
||||
- "B(WARNING:) you have to make sure that private key data is not accidentally logged!"
|
||||
type: bool
|
||||
default: false
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.name_encoding
|
||||
seealso:
|
||||
- module: community.crypto.openssl_privatekey_info
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Show the Subject Alt Names of the CSR
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{
|
||||
(
|
||||
lookup('ansible.builtin.file', '/path/to/cert.csr')
|
||||
| community.crypto.openssl_privatekey_info
|
||||
).subject_alt_name | join(', ')
|
||||
}}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description:
|
||||
- Information on the certificate.
|
||||
type: dict
|
||||
contains:
|
||||
public_key:
|
||||
description: Private key's public key in PEM format.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
|
||||
public_key_fingerprints:
|
||||
description:
|
||||
- Fingerprints of private key's public key.
|
||||
- For every hash algorithm available, the fingerprint is computed.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
|
||||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
|
||||
type:
|
||||
description:
|
||||
- The key's type.
|
||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
||||
- Will start with C(unknown) if the key type cannot be determined.
|
||||
returned: success
|
||||
type: str
|
||||
sample: RSA
|
||||
public_data:
|
||||
description:
|
||||
- Public key data. Depends on key type.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
size:
|
||||
description:
|
||||
- Bit size of modulus (RSA) or prime number (DSA).
|
||||
type: int
|
||||
returned: When C(type=RSA) or C(type=DSA)
|
||||
modulus:
|
||||
description:
|
||||
- The RSA key's modulus.
|
||||
type: int
|
||||
returned: When C(type=RSA)
|
||||
exponent:
|
||||
description:
|
||||
- The RSA key's public exponent.
|
||||
type: int
|
||||
returned: When C(type=RSA)
|
||||
p:
|
||||
description:
|
||||
- The C(p) value for DSA.
|
||||
- This is the prime modulus upon which arithmetic takes place.
|
||||
type: int
|
||||
returned: When C(type=DSA)
|
||||
q:
|
||||
description:
|
||||
- The C(q) value for DSA.
|
||||
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
|
||||
multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(type=DSA)
|
||||
g:
|
||||
description:
|
||||
- The C(g) value for DSA.
|
||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(type=DSA)
|
||||
curve:
|
||||
description:
|
||||
- The curve's name for ECC.
|
||||
type: str
|
||||
returned: When C(type=ECC)
|
||||
exponent_size:
|
||||
description:
|
||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||
type: int
|
||||
returned: When C(type=ECC)
|
||||
x:
|
||||
description:
|
||||
- The C(x) coordinate for the public point on the elliptic curve.
|
||||
type: int
|
||||
returned: When C(type=ECC)
|
||||
y:
|
||||
description:
|
||||
- For C(type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
||||
- For C(type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
|
||||
type: int
|
||||
returned: When C(type=DSA) or C(type=ECC)
|
||||
private_data:
|
||||
description:
|
||||
- Private key data. Depends on key type.
|
||||
returned: success and when I(return_private_key_data) is set to C(true)
|
||||
type: dict
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.privatekey_info import (
|
||||
PrivateKeyParseError,
|
||||
get_privatekey_info,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
|
||||
|
||||
|
||||
def openssl_privatekey_info_filter(data, passphrase=None, return_private_key_data=False):
|
||||
'''Extract information from X.509 PEM certificate.'''
|
||||
if not isinstance(data, string_types):
|
||||
raise AnsibleFilterError('The community.crypto.openssl_privatekey_info input must be a text type, not %s' % type(data))
|
||||
if passphrase is not None and not isinstance(passphrase, string_types):
|
||||
raise AnsibleFilterError('The passphrase option must be a text type, not %s' % type(passphrase))
|
||||
if not isinstance(return_private_key_data, bool):
|
||||
raise AnsibleFilterError('The return_private_key_data option must be a boolean, not %s' % type(return_private_key_data))
|
||||
|
||||
module = FilterModuleMock({})
|
||||
try:
|
||||
result = get_privatekey_info(module, 'cryptography', content=to_bytes(data), passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
result.pop('can_parse_key', None)
|
||||
result.pop('key_is_consistent', None)
|
||||
return result
|
||||
except PrivateKeyParseError as exc:
|
||||
raise AnsibleFilterError(exc.error_message)
|
||||
except OpenSSLObjectError as exc:
|
||||
raise AnsibleFilterError(to_native(exc))
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
'''Ansible jinja2 filters'''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'openssl_privatekey_info': openssl_privatekey_info_filter,
|
||||
}
|
||||
162
plugins/filter/openssl_publickey_info.py
Normal file
162
plugins/filter/openssl_publickey_info.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: openssl_publickey_info
|
||||
short_description: Retrieve information from OpenSSL public keys in PEM format
|
||||
version_added: 2.10.0
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Provided a public key in OpenSSL PEM format, retrieve information.
|
||||
- This is a filter version of the M(community.crypto.openssl_publickey_info) module.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The content of the OpenSSL PEM public key.
|
||||
type: string
|
||||
required: true
|
||||
seealso:
|
||||
- module: community.crypto.openssl_publickey_info
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Show the type of a public key
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{
|
||||
(
|
||||
lookup('ansible.builtin.file', '/path/to/public-key.pem')
|
||||
| community.crypto.openssl_publickey_info
|
||||
).type
|
||||
}}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description:
|
||||
- Information on the public key.
|
||||
type: dict
|
||||
contains:
|
||||
fingerprints:
|
||||
description:
|
||||
- Fingerprints of public key.
|
||||
- For every hash algorithm available, the fingerprint is computed.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
|
||||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
|
||||
type:
|
||||
description:
|
||||
- The key's type.
|
||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
||||
- Will start with C(unknown) if the key type cannot be determined.
|
||||
returned: success
|
||||
type: str
|
||||
sample: RSA
|
||||
public_data:
|
||||
description:
|
||||
- Public key data. Depends on key type.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
size:
|
||||
description:
|
||||
- Bit size of modulus (RSA) or prime number (DSA).
|
||||
type: int
|
||||
returned: When C(type=RSA) or C(type=DSA)
|
||||
modulus:
|
||||
description:
|
||||
- The RSA key's modulus.
|
||||
type: int
|
||||
returned: When C(type=RSA)
|
||||
exponent:
|
||||
description:
|
||||
- The RSA key's public exponent.
|
||||
type: int
|
||||
returned: When C(type=RSA)
|
||||
p:
|
||||
description:
|
||||
- The C(p) value for DSA.
|
||||
- This is the prime modulus upon which arithmetic takes place.
|
||||
type: int
|
||||
returned: When C(type=DSA)
|
||||
q:
|
||||
description:
|
||||
- The C(q) value for DSA.
|
||||
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
|
||||
multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(type=DSA)
|
||||
g:
|
||||
description:
|
||||
- The C(g) value for DSA.
|
||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(type=DSA)
|
||||
curve:
|
||||
description:
|
||||
- The curve's name for ECC.
|
||||
type: str
|
||||
returned: When C(type=ECC)
|
||||
exponent_size:
|
||||
description:
|
||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||
type: int
|
||||
returned: When C(type=ECC)
|
||||
x:
|
||||
description:
|
||||
- The C(x) coordinate for the public point on the elliptic curve.
|
||||
type: int
|
||||
returned: When C(type=ECC)
|
||||
y:
|
||||
description:
|
||||
- For C(type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
||||
- For C(type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
|
||||
type: int
|
||||
returned: When C(type=DSA) or C(type=ECC)
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
PublicKeyParseError,
|
||||
get_publickey_info,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
|
||||
|
||||
|
||||
def openssl_publickey_info_filter(data):
|
||||
'''Extract information from OpenSSL PEM public key.'''
|
||||
if not isinstance(data, string_types):
|
||||
raise AnsibleFilterError('The community.crypto.openssl_publickey_info input must be a text type, not %s' % type(data))
|
||||
|
||||
module = FilterModuleMock({})
|
||||
try:
|
||||
return get_publickey_info(module, 'cryptography', content=to_bytes(data))
|
||||
except PublicKeyParseError as exc:
|
||||
raise AnsibleFilterError(exc.error_message)
|
||||
except OpenSSLObjectError as exc:
|
||||
raise AnsibleFilterError(to_native(exc))
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
'''Ansible jinja2 filters'''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'openssl_publickey_info': openssl_publickey_info_filter,
|
||||
}
|
||||
64
plugins/filter/split_pem.py
Normal file
64
plugins/filter/split_pem.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: split_pem
|
||||
short_description: Split PEM file contents into multiple objects
|
||||
version_added: 2.10.0
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Split PEM file contents into multiple PEM objects. Comments or invalid parts are ignored.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The PEM contents to split.
|
||||
type: string
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Print all CA certificates
|
||||
ansible.builtin.debug:
|
||||
msg: '{{ item }}'
|
||||
loop: >-
|
||||
{{ lookup('ansible.builtin.file', '/path/to/ca-bundle.pem') | community.crypto.split_pem }}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description:
|
||||
- A list of PEM file contents.
|
||||
type: list
|
||||
elements: string
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import split_pem_list
|
||||
|
||||
|
||||
def split_pem_filter(data):
|
||||
'''Split PEM file.'''
|
||||
if not isinstance(data, string_types):
|
||||
raise AnsibleFilterError('The community.crypto.split_pem input must be a text type, not %s' % type(data))
|
||||
|
||||
data = to_text(data)
|
||||
return split_pem_list(data)
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
'''Ansible jinja2 filters'''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'split_pem': split_pem_filter,
|
||||
}
|
||||
346
plugins/filter/x509_certificate_info.py
Normal file
346
plugins/filter/x509_certificate_info.py
Normal file
@@ -0,0 +1,346 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: x509_certificate_info
|
||||
short_description: Retrieve information from X.509 certificates in PEM format
|
||||
version_added: 2.10.0
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Provided a X.509 certificate in PEM format, retrieve information.
|
||||
- This is a filter version of the M(community.crypto.x509_certificate_info) module.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The content of the X.509 certificate in PEM format.
|
||||
type: string
|
||||
required: true
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.name_encoding
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate_info
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Show the Subject Alt Names of the certificate
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{
|
||||
(
|
||||
lookup('ansible.builtin.file', '/path/to/cert.pem')
|
||||
| community.crypto.x509_certificate_info
|
||||
).subject_alt_name | join(', ')
|
||||
}}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description:
|
||||
- Information on the certificate.
|
||||
type: dict
|
||||
contains:
|
||||
expired:
|
||||
description: Whether the certificate is expired (in other words, C(notAfter) is in the past).
|
||||
returned: success
|
||||
type: bool
|
||||
basic_constraints:
|
||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ["CA:TRUE", "pathlen:1"]
|
||||
basic_constraints_critical:
|
||||
description: Whether the C(basic_constraints) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extended_key_usage:
|
||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: [Biometric Info, DVCS, Time Stamping]
|
||||
extended_key_usage_critical:
|
||||
description: Whether the C(extended_key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extensions_by_oid:
|
||||
description: Returns a dictionary for every extension OID.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
critical:
|
||||
description: Whether the extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
value:
|
||||
description:
|
||||
- The Base64 encoded value (in DER format) of the extension.
|
||||
- B(Note) that depending on the C(cryptography) version used, it is
|
||||
not possible to extract the ASN.1 content of the extension, but only
|
||||
to provide the re-encoded content of the extension in case it was
|
||||
parsed by C(cryptography). This should usually result in exactly the
|
||||
same value, except if the original extension value was malformed.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "MAMCAQU="
|
||||
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
||||
key_usage:
|
||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: [Key Agreement, Data Encipherment]
|
||||
key_usage_critical:
|
||||
description: Whether the C(key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
subject_alt_name:
|
||||
description:
|
||||
- Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
||||
- See I(name_encoding) for how IDNs are handled.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
|
||||
subject_alt_name_critical:
|
||||
description: Whether the C(subject_alt_name) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple:
|
||||
description: C(true) if the OCSP Must Staple extension is present, C(none) otherwise.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple_critical:
|
||||
description: Whether the C(ocsp_must_staple) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
issuer:
|
||||
description:
|
||||
- The certificate's issuer.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
|
||||
issuer_ordered:
|
||||
description: The certificate's issuer as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: [["organizationName", "Ansible"], ["commonName": "ca.example.com"]]
|
||||
subject:
|
||||
description:
|
||||
- The certificate's subject as a dictionary.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: {"commonName": "www.example.com", "emailAddress": "test@example.com"}
|
||||
subject_ordered:
|
||||
description: The certificate's subject as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: [["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]
|
||||
not_after:
|
||||
description: C(notAfter) date as ASN.1 TIME.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '20190413202428Z'
|
||||
not_before:
|
||||
description: C(notBefore) date as ASN.1 TIME.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '20190331202428Z'
|
||||
public_key:
|
||||
description: Certificate's public key in PEM format.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
|
||||
public_key_type:
|
||||
description:
|
||||
- The certificate's public key's type.
|
||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
||||
- Will start with C(unknown) if the key type cannot be determined.
|
||||
returned: success
|
||||
type: str
|
||||
sample: RSA
|
||||
public_key_data:
|
||||
description:
|
||||
- Public key data. Depends on the public key's type.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
size:
|
||||
description:
|
||||
- Bit size of modulus (RSA) or prime number (DSA).
|
||||
type: int
|
||||
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
|
||||
modulus:
|
||||
description:
|
||||
- The RSA key's modulus.
|
||||
type: int
|
||||
returned: When C(public_key_type=RSA)
|
||||
exponent:
|
||||
description:
|
||||
- The RSA key's public exponent.
|
||||
type: int
|
||||
returned: When C(public_key_type=RSA)
|
||||
p:
|
||||
description:
|
||||
- The C(p) value for DSA.
|
||||
- This is the prime modulus upon which arithmetic takes place.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA)
|
||||
q:
|
||||
description:
|
||||
- The C(q) value for DSA.
|
||||
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
|
||||
multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA)
|
||||
g:
|
||||
description:
|
||||
- The C(g) value for DSA.
|
||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA)
|
||||
curve:
|
||||
description:
|
||||
- The curve's name for ECC.
|
||||
type: str
|
||||
returned: When C(public_key_type=ECC)
|
||||
exponent_size:
|
||||
description:
|
||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||
type: int
|
||||
returned: When C(public_key_type=ECC)
|
||||
x:
|
||||
description:
|
||||
- The C(x) coordinate for the public point on the elliptic curve.
|
||||
type: int
|
||||
returned: When C(public_key_type=ECC)
|
||||
y:
|
||||
description:
|
||||
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
||||
- For C(public_key_type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
|
||||
type: int
|
||||
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
|
||||
public_key_fingerprints:
|
||||
description:
|
||||
- Fingerprints of certificate's public key.
|
||||
- For every hash algorithm available, the fingerprint is computed.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
|
||||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
|
||||
fingerprints:
|
||||
description:
|
||||
- Fingerprints of the DER-encoded form of the whole certificate.
|
||||
- For every hash algorithm available, the fingerprint is computed.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
|
||||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
|
||||
signature_algorithm:
|
||||
description: The signature algorithm used to sign the certificate.
|
||||
returned: success
|
||||
type: str
|
||||
sample: sha256WithRSAEncryption
|
||||
serial_number:
|
||||
description: The certificate's serial number.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 1234
|
||||
version:
|
||||
description: The certificate version.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 3
|
||||
subject_key_identifier:
|
||||
description:
|
||||
- The certificate's subject key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
authority_key_identifier:
|
||||
description:
|
||||
- The certificate's authority key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
authority_cert_issuer:
|
||||
description:
|
||||
- The certificate's authority cert issuer as a list of general names.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
- See I(name_encoding) for how IDNs are handled.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
|
||||
authority_cert_serial_number:
|
||||
description:
|
||||
- The certificate's authority cert serial number.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 12345
|
||||
ocsp_uri:
|
||||
description: The OCSP responder URI, if included in the certificate. Will be
|
||||
C(none) if no OCSP responder URI is included.
|
||||
returned: success
|
||||
type: str
|
||||
issuer_uri:
|
||||
description: The Issuer URI, if included in the certificate. Will be
|
||||
C(none) if no issuer URI is included.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import (
|
||||
get_certificate_info,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
|
||||
|
||||
|
||||
def x509_certificate_info_filter(data, name_encoding='ignore'):
|
||||
'''Extract information from X.509 PEM certificate.'''
|
||||
if not isinstance(data, string_types):
|
||||
raise AnsibleFilterError('The community.crypto.x509_certificate_info input must be a text type, not %s' % type(data))
|
||||
if not isinstance(name_encoding, string_types):
|
||||
raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding))
|
||||
name_encoding = to_native(name_encoding)
|
||||
if name_encoding not in ('ignore', 'idna', 'unicode'):
|
||||
raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding)
|
||||
|
||||
module = FilterModuleMock({'name_encoding': name_encoding})
|
||||
try:
|
||||
return get_certificate_info(module, 'cryptography', content=to_bytes(data))
|
||||
except OpenSSLObjectError as exc:
|
||||
raise AnsibleFilterError(to_native(exc))
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
'''Ansible jinja2 filters'''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'x509_certificate_info': x509_certificate_info_filter,
|
||||
}
|
||||
196
plugins/filter/x509_crl_info.py
Normal file
196
plugins/filter/x509_crl_info.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: x509_crl_info
|
||||
short_description: Retrieve information from X.509 CRLs in PEM format
|
||||
version_added: 2.10.0
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Provided a X.509 crl in PEM format, retrieve information.
|
||||
- This is a filter version of the M(community.crypto.x509_crl_info) module.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The content of the X.509 CRL in PEM format.
|
||||
type: string
|
||||
required: true
|
||||
list_revoked_certificates:
|
||||
description:
|
||||
- If set to C(false), the list of revoked certificates is not included in the result.
|
||||
- This is useful when retrieving information on large CRL files. Enumerating all revoked
|
||||
certificates can take some time, including serializing the result as JSON, sending it to
|
||||
the Ansible controller, and decoding it again.
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 1.7.0
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.name_encoding
|
||||
seealso:
|
||||
- module: community.crypto.x509_crl_info
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Show the Organization Name of the CRL's subject
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{
|
||||
(
|
||||
lookup('ansible.builtin.file', '/path/to/cert.pem')
|
||||
| community.crypto.x509_crl_info
|
||||
).issuer.organizationName
|
||||
}}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description:
|
||||
- Information on the CRL.
|
||||
type: dict
|
||||
contains:
|
||||
format:
|
||||
description:
|
||||
- Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)).
|
||||
returned: success
|
||||
type: str
|
||||
sample: pem
|
||||
issuer:
|
||||
description:
|
||||
- The CRL's issuer.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
- See I(name_encoding) for how IDNs are handled.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
|
||||
issuer_ordered:
|
||||
description: The CRL's issuer as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: [["organizationName", "Ansible"], ["commonName": "ca.example.com"]]
|
||||
last_update:
|
||||
description: The point in time from which this CRL can be trusted as ASN.1 TIME.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '20190413202428Z'
|
||||
next_update:
|
||||
description: The point in time from which a new CRL will be issued and the client has to check for it as ASN.1 TIME.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '20190413202428Z'
|
||||
digest:
|
||||
description: The signature algorithm used to sign the CRL.
|
||||
returned: success
|
||||
type: str
|
||||
sample: sha256WithRSAEncryption
|
||||
revoked_certificates:
|
||||
description: List of certificates to be revoked.
|
||||
returned: success if I(list_revoked_certificates=true)
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
serial_number:
|
||||
description: Serial number of the certificate.
|
||||
type: int
|
||||
sample: 1234
|
||||
revocation_date:
|
||||
description: The point in time the certificate was revoked as ASN.1 TIME.
|
||||
type: str
|
||||
sample: '20190413202428Z'
|
||||
issuer:
|
||||
description:
|
||||
- The certificate's issuer.
|
||||
- See I(name_encoding) for how IDNs are handled.
|
||||
type: list
|
||||
elements: str
|
||||
sample: ["DNS:ca.example.org"]
|
||||
issuer_critical:
|
||||
description: Whether the certificate issuer extension is critical.
|
||||
type: bool
|
||||
sample: false
|
||||
reason:
|
||||
description:
|
||||
- The value for the revocation reason extension.
|
||||
- One of C(unspecified), C(key_compromise), C(ca_compromise), C(affiliation_changed), C(superseded),
|
||||
C(cessation_of_operation), C(certificate_hold), C(privilege_withdrawn), C(aa_compromise), and
|
||||
C(remove_from_crl).
|
||||
type: str
|
||||
sample: key_compromise
|
||||
reason_critical:
|
||||
description: Whether the revocation reason extension is critical.
|
||||
type: bool
|
||||
sample: false
|
||||
invalidity_date:
|
||||
description: |
|
||||
The point in time it was known/suspected that the private key was compromised
|
||||
or that the certificate otherwise became invalid as ASN.1 TIME.
|
||||
type: str
|
||||
sample: '20190413202428Z'
|
||||
invalidity_date_critical:
|
||||
description: Whether the invalidity date extension is critical.
|
||||
type: bool
|
||||
sample: false
|
||||
'''
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||
identify_pem_format,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.crl_info import (
|
||||
get_crl_info,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
|
||||
|
||||
|
||||
def x509_crl_info_filter(data, name_encoding='ignore', list_revoked_certificates=True):
|
||||
'''Extract information from X.509 PEM certificate.'''
|
||||
if not isinstance(data, string_types):
|
||||
raise AnsibleFilterError('The community.crypto.x509_crl_info input must be a text type, not %s' % type(data))
|
||||
if not isinstance(name_encoding, string_types):
|
||||
raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding))
|
||||
if not isinstance(list_revoked_certificates, bool):
|
||||
raise AnsibleFilterError('The list_revoked_certificates option must be a boolean, not %s' % type(list_revoked_certificates))
|
||||
name_encoding = to_native(name_encoding)
|
||||
if name_encoding not in ('ignore', 'idna', 'unicode'):
|
||||
raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding)
|
||||
|
||||
data = to_bytes(data)
|
||||
if not identify_pem_format(data):
|
||||
try:
|
||||
data = base64.b64decode(to_native(data))
|
||||
except (binascii.Error, TypeError, ValueError, UnicodeEncodeError) as e:
|
||||
pass
|
||||
|
||||
module = FilterModuleMock({'name_encoding': name_encoding})
|
||||
try:
|
||||
return get_crl_info(module, content=data, list_revoked_certificates=list_revoked_certificates)
|
||||
except OpenSSLObjectError as exc:
|
||||
raise AnsibleFilterError(to_native(exc))
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
'''Ansible jinja2 filters'''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'x509_crl_info': x509_crl_info_filter,
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
# Implements multiple version numbering conventions for the
|
||||
# Python Module Distribution Utilities.
|
||||
#
|
||||
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
|
||||
# Copyright (c) 2001-2022 Python Software Foundation. All rights reserved.
|
||||
# PSF License (see LICENSES/PSF-2.0.txt or https://opensource.org/licenses/Python-2.0)
|
||||
# SPDX-License-Identifier: PSF-2.0
|
||||
#
|
||||
|
||||
"""Provides classes to represent module version numbers (one class for
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import copy
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import locale
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils.six.moves.urllib.parse import unquote
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
get_default_argspec,
|
||||
ACMEDirectory,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
|
||||
CryptographyBackend,
|
||||
CRYPTOGRAPHY_VERSION,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
||||
OpenSSLCLIBackend,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme._compatibility import (
|
||||
handle_standard_module_arguments,
|
||||
set_crypto_backend,
|
||||
HAS_CURRENT_CRYPTOGRAPHY,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme._compatibility import ACMELegacyAccount as ACMEAccount
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
|
||||
read_file,
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||
nopad_b64,
|
||||
pem_to_der,
|
||||
process_links,
|
||||
)
|
||||
|
||||
|
||||
def openssl_get_csr_identifiers(openssl_binary, module, csr_filename, csr_content=None):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
return OpenSSLCLIBackend(module, openssl_binary=openssl_binary).get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)
|
||||
|
||||
|
||||
def cryptography_get_csr_identifiers(module, csr_filename, csr_content=None):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
return CryptographyBackend(module).get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)
|
||||
|
||||
|
||||
def cryptography_get_cert_days(module, cert_file, now=None):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
return CryptographyBackend(module).get_cert_days(cert_filename=cert_file, now=now)
|
||||
@@ -1,267 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import locale
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import HAS_CURRENT_CRYPTOGRAPHY as _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
|
||||
CryptographyBackend,
|
||||
CRYPTOGRAPHY_VERSION,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
||||
OpenSSLCLIBackend,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
ACMEClient,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.account import (
|
||||
ACMEAccount,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
|
||||
create_key_authorization,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
||||
KeyParsingError,
|
||||
)
|
||||
|
||||
|
||||
HAS_CURRENT_CRYPTOGRAPHY = _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY
|
||||
|
||||
|
||||
def set_crypto_backend(module):
|
||||
'''
|
||||
Sets which crypto backend to use (default: auto detection).
|
||||
|
||||
Does not care whether a new enough cryptoraphy is available or not. Must
|
||||
be called before any real stuff is done which might evaluate
|
||||
``HAS_CURRENT_CRYPTOGRAPHY``.
|
||||
'''
|
||||
global HAS_CURRENT_CRYPTOGRAPHY
|
||||
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
# Choose backend
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
pass
|
||||
elif backend == 'openssl':
|
||||
HAS_CURRENT_CRYPTOGRAPHY = False
|
||||
elif backend == 'cryptography':
|
||||
if not _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY:
|
||||
module.fail_json(msg=missing_required_lib('cryptography'))
|
||||
HAS_CURRENT_CRYPTOGRAPHY = True
|
||||
else:
|
||||
module.fail_json(msg='Unknown crypto backend "{0}"!'.format(backend))
|
||||
# Inform about choices
|
||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||
module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION))
|
||||
return 'cryptography'
|
||||
else:
|
||||
module.debug('Using OpenSSL binary backend')
|
||||
return 'openssl'
|
||||
|
||||
|
||||
def handle_standard_module_arguments(module, needs_acme_v2=False):
|
||||
'''
|
||||
Do standard module setup, argument handling and warning emitting.
|
||||
'''
|
||||
backend = set_crypto_backend(module)
|
||||
|
||||
if not module.params['validate_certs']:
|
||||
module.warn(
|
||||
'Disabling certificate validation for communications with ACME endpoint. '
|
||||
'This should only be done for testing against a local ACME server for '
|
||||
'development purposes, but *never* for production purposes.'
|
||||
)
|
||||
|
||||
if module.params['acme_version'] is None:
|
||||
module.params['acme_version'] = 1
|
||||
module.deprecate("The option 'acme_version' will be required from community.crypto 2.0.0 on",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
if module.params['acme_directory'] is None:
|
||||
module.params['acme_directory'] = 'https://acme-staging.api.letsencrypt.org/directory'
|
||||
module.deprecate("The option 'acme_directory' will be required from community.crypto 2.0.0 on",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
if needs_acme_v2 and module.params['acme_version'] < 2:
|
||||
module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name))
|
||||
|
||||
# AnsibleModule() changes the locale, so change it back to C because we rely on time.strptime() when parsing certificate dates.
|
||||
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
def get_compatibility_backend(module):
|
||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||
return CryptographyBackend(module)
|
||||
else:
|
||||
return OpenSSLCLIBackend(module)
|
||||
|
||||
|
||||
class ACMELegacyAccount(object):
|
||||
'''
|
||||
ACME account object. Handles the authorized communication with the
|
||||
ACME server. Provides access to account bound information like
|
||||
the currently active authorizations and valid certificates
|
||||
'''
|
||||
|
||||
def __init__(self, module):
|
||||
module.deprecate(
|
||||
'Please adjust your custom module/plugin to the ACME module_utils refactor '
|
||||
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
|
||||
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
|
||||
'your code', version='2.0.0', collection_name='community.crypto')
|
||||
backend = get_compatibility_backend(module)
|
||||
self.client = ACMEClient(module, backend)
|
||||
self.account = ACMEAccount(self.client)
|
||||
self.key = self.client.account_key_file
|
||||
self.key_content = self.client.account_key_content
|
||||
self.uri = self.client.account_uri
|
||||
self.key_data = self.client.account_key_data
|
||||
self.jwk = self.client.account_jwk
|
||||
self.jws_header = self.client.account_jws_header
|
||||
self.directory = self.client.directory
|
||||
|
||||
def get_keyauthorization(self, token):
|
||||
'''
|
||||
Returns the key authorization for the given token
|
||||
https://tools.ietf.org/html/rfc8555#section-8.1
|
||||
'''
|
||||
return create_key_authorization(self.client, token)
|
||||
|
||||
def parse_key(self, key_file=None, key_content=None):
|
||||
'''
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns a pair
|
||||
(error, key_data).
|
||||
'''
|
||||
try:
|
||||
return None, self.client.parse_key(key_file=key_file, key_content=key_content)
|
||||
except KeyParsingError as e:
|
||||
return e.msg, {}
|
||||
|
||||
def sign_request(self, protected, payload, key_data, encode_payload=True):
|
||||
return self.client.sign_request(protected, payload, key_data, encode_payload=encode_payload)
|
||||
|
||||
def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True, encode_payload=True):
|
||||
'''
|
||||
Sends a JWS signed HTTP POST request to the ACME server and returns
|
||||
the response as dictionary
|
||||
https://tools.ietf.org/html/rfc8555#section-6.2
|
||||
|
||||
If payload is None, a POST-as-GET is performed.
|
||||
(https://tools.ietf.org/html/rfc8555#section-6.3)
|
||||
'''
|
||||
return self.client.send_signed_request(
|
||||
url,
|
||||
payload,
|
||||
key_data=key_data,
|
||||
jws_header=jws_header,
|
||||
parse_json_result=parse_json_result,
|
||||
encode_payload=encode_payload,
|
||||
fail_on_error=False,
|
||||
)
|
||||
|
||||
def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, fail_on_error=True):
|
||||
'''
|
||||
Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback
|
||||
to GET if server replies with a status code of 405.
|
||||
'''
|
||||
return self.client.get_request(
|
||||
uri,
|
||||
parse_json_result=parse_json_result,
|
||||
headers=headers,
|
||||
get_only=get_only,
|
||||
fail_on_error=fail_on_error,
|
||||
)
|
||||
|
||||
def set_account_uri(self, uri):
|
||||
'''
|
||||
Set account URI. For ACME v2, it needs to be used to sending signed
|
||||
requests.
|
||||
'''
|
||||
self.client.set_account_uri(uri)
|
||||
self.uri = self.client.account_uri
|
||||
|
||||
def get_account_data(self):
|
||||
'''
|
||||
Retrieve account information. Can only be called when the account
|
||||
URI is already known (such as after calling setup_account).
|
||||
Return None if the account was deactivated, or a dict otherwise.
|
||||
'''
|
||||
return self.account.get_account_data()
|
||||
|
||||
def setup_account(self, contact=None, agreement=None, terms_agreed=False,
|
||||
allow_creation=True, remove_account_uri_if_not_exists=False,
|
||||
external_account_binding=None):
|
||||
'''
|
||||
Detect or create an account on the ACME server. For ACME v1,
|
||||
as the only way (without knowing an account URI) to test if an
|
||||
account exists is to try and create one with the provided account
|
||||
key, this method will always result in an account being present
|
||||
(except on error situations). For ACME v2, a new account will
|
||||
only be created if ``allow_creation`` is set to True.
|
||||
|
||||
For ACME v2, ``check_mode`` is fully respected. For ACME v1, the
|
||||
account might be created if it does not yet exist.
|
||||
|
||||
Return a pair ``(created, account_data)``. Here, ``created`` will
|
||||
be ``True`` in case the account was created or would be created
|
||||
(check mode). ``account_data`` will be the current account data,
|
||||
or ``None`` if the account does not exist.
|
||||
|
||||
The account URI will be stored in ``self.uri``; if it is ``None``,
|
||||
the account does not exist.
|
||||
|
||||
If specified, ``external_account_binding`` should be a dictionary
|
||||
with keys ``kid``, ``alg`` and ``key``
|
||||
(https://tools.ietf.org/html/rfc8555#section-7.3.4).
|
||||
|
||||
https://tools.ietf.org/html/rfc8555#section-7.3
|
||||
'''
|
||||
result = self.account.setup_account(
|
||||
contact=contact,
|
||||
agreement=agreement,
|
||||
terms_agreed=terms_agreed,
|
||||
allow_creation=allow_creation,
|
||||
remove_account_uri_if_not_exists=remove_account_uri_if_not_exists,
|
||||
external_account_binding=external_account_binding,
|
||||
)
|
||||
self.uri = self.client.account_uri
|
||||
return result
|
||||
|
||||
def update_account(self, account_data, contact=None):
|
||||
'''
|
||||
Update an account on the ACME server. Check mode is fully respected.
|
||||
|
||||
The current account data must be provided as ``account_data``.
|
||||
|
||||
Return a pair ``(updated, account_data)``, where ``updated`` is
|
||||
``True`` in case something changed (contact info updated) or
|
||||
would be changed (check mode), and ``account_data`` the updated
|
||||
account data.
|
||||
|
||||
https://tools.ietf.org/html/rfc8555#section-7.3.2
|
||||
'''
|
||||
return self.account.update_account(account_data, contact=contact)
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -61,7 +62,7 @@ class ACMEAccount(object):
|
||||
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
|
||||
# to see whether the account already exists.
|
||||
|
||||
# Note that we pass contact here: ZeroSSL does not accept regisration calls without contacts, even
|
||||
# Note that we pass contact here: ZeroSSL does not accept registration calls without contacts, even
|
||||
# if onlyReturnExisting is set to true.
|
||||
created, data = self._new_reg(contact=contact, allow_creation=False)
|
||||
if data:
|
||||
@@ -118,10 +119,10 @@ class ACMEAccount(object):
|
||||
self.client.set_account_uri(info['location'])
|
||||
return False, result
|
||||
elif info['status'] == 400 and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation:
|
||||
# Account does not exist (and we didn't try to create it)
|
||||
# Account does not exist (and we did not try to create it)
|
||||
return False, None
|
||||
elif info['status'] == 403 and result['type'] == 'urn:ietf:params:acme:error:unauthorized' and 'deactivated' in (result.get('detail') or ''):
|
||||
# Account has been deactivated; currently works for Pebble; hasn't been
|
||||
# Account has been deactivated; currently works for Pebble; has not been
|
||||
# implemented for Boulder (https://github.com/letsencrypt/boulder/issues/3971),
|
||||
# might need adjustment in error detection.
|
||||
if not allow_creation:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -12,6 +13,8 @@ import copy
|
||||
import datetime
|
||||
import json
|
||||
import locale
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
@@ -24,6 +27,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.backend_open
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
|
||||
CryptographyBackend,
|
||||
CRYPTOGRAPHY_ERROR,
|
||||
CRYPTOGRAPHY_MINIMAL_VERSION,
|
||||
CRYPTOGRAPHY_VERSION,
|
||||
HAS_CURRENT_CRYPTOGRAPHY,
|
||||
)
|
||||
@@ -33,12 +38,43 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
NetworkException,
|
||||
ModuleFailException,
|
||||
KeyParsingError,
|
||||
format_http_status,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||
nopad_b64,
|
||||
)
|
||||
|
||||
try:
|
||||
import ipaddress # noqa: F401, pylint: disable=unused-import
|
||||
except ImportError:
|
||||
HAS_IPADDRESS = False
|
||||
IPADDRESS_IMPORT_ERROR = traceback.format_exc()
|
||||
else:
|
||||
HAS_IPADDRESS = True
|
||||
IPADDRESS_IMPORT_ERROR = None
|
||||
|
||||
|
||||
RETRY_STATUS_CODES = (408, 429, 503)
|
||||
|
||||
|
||||
def _decode_retry(module, response, info, retry_count):
|
||||
if info['status'] not in RETRY_STATUS_CODES:
|
||||
return False
|
||||
|
||||
if retry_count >= 5:
|
||||
raise ACMEProtocolException(module, msg='Giving up after 5 retries', info=info, response=response)
|
||||
|
||||
# 429 and 503 should have a Retry-After header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
|
||||
try:
|
||||
retry_after = min(max(1, int(info.get('retry-after'))), 60)
|
||||
except (TypeError, ValueError) as dummy:
|
||||
retry_after = 10
|
||||
module.log('Retrieved a %s HTTP status on %s, retrying in %s seconds' % (format_http_status(info['status']), info['url'], retry_after))
|
||||
|
||||
time.sleep(retry_after)
|
||||
return True
|
||||
|
||||
|
||||
def _assert_fetch_url_success(module, response, info, allow_redirect=False, allow_client_error=True, allow_server_error=True):
|
||||
if info['status'] < 0:
|
||||
@@ -74,6 +110,8 @@ class ACMEDirectory(object):
|
||||
|
||||
self.directory, dummy = account.get_request(self.directory_root, get_only=True)
|
||||
|
||||
self.request_timeout = module.params['request_timeout']
|
||||
|
||||
# Check whether self.version matches what we expect
|
||||
if self.version == 1:
|
||||
for key in ('new-reg', 'new-authz', 'new-cert'):
|
||||
@@ -94,10 +132,22 @@ class ACMEDirectory(object):
|
||||
url = self.directory_root if self.version == 1 else self.directory['newNonce']
|
||||
if resource is not None:
|
||||
url = resource
|
||||
dummy, info = fetch_url(self.module, url, method='HEAD')
|
||||
if info['status'] not in (200, 204):
|
||||
raise NetworkException("Failed to get replay-nonce, got status {0}".format(info['status']))
|
||||
return info['replay-nonce']
|
||||
retry_count = 0
|
||||
while True:
|
||||
response, info = fetch_url(self.module, url, method='HEAD', timeout=self.request_timeout)
|
||||
if _decode_retry(self.module, response, info, retry_count):
|
||||
retry_count += 1
|
||||
continue
|
||||
if info['status'] not in (200, 204):
|
||||
raise NetworkException("Failed to get replay-nonce, got status {0}".format(format_http_status(info['status'])))
|
||||
if 'replay-nonce' in info:
|
||||
return info['replay-nonce']
|
||||
self.module.log(
|
||||
'HEAD to {0} did return status {1}, but no replay-nonce header!'.format(url, format_http_status(info['status'])))
|
||||
if retry_count >= 5:
|
||||
raise ACMEProtocolException(
|
||||
self.module, msg='Was not able to obtain nonce, giving up after 5 retries', info=info, response=response)
|
||||
retry_count += 1
|
||||
|
||||
|
||||
class ACMEClient(object):
|
||||
@@ -122,6 +172,8 @@ class ACMEClient(object):
|
||||
# Make sure empty string is treated as None.
|
||||
self.account_uri = module.params.get('account_uri') or None
|
||||
|
||||
self.request_timeout = module.params['request_timeout']
|
||||
|
||||
self.account_key_data = None
|
||||
self.account_jwk = None
|
||||
self.account_jws_header = None
|
||||
@@ -226,7 +278,10 @@ class ACMEClient(object):
|
||||
headers = {
|
||||
'Content-Type': 'application/jose+json',
|
||||
}
|
||||
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', timeout=self.request_timeout)
|
||||
if _decode_retry(self.module, resp, info, failed_tries):
|
||||
failed_tries += 1
|
||||
continue
|
||||
_assert_fetch_url_success(self.module, resp, info)
|
||||
result = {}
|
||||
|
||||
@@ -285,7 +340,12 @@ class ACMEClient(object):
|
||||
|
||||
if get_only:
|
||||
# Perform unauthenticated GET
|
||||
resp, info = fetch_url(self.module, uri, method='GET', headers=headers)
|
||||
retry_count = 0
|
||||
while True:
|
||||
resp, info = fetch_url(self.module, uri, method='GET', headers=headers, timeout=self.request_timeout)
|
||||
if not _decode_retry(self.module, resp, info, retry_count):
|
||||
break
|
||||
retry_count += 1
|
||||
|
||||
_assert_fetch_url_success(self.module, resp, info)
|
||||
|
||||
@@ -329,14 +389,18 @@ def get_default_argspec():
|
||||
account_key_content=dict(type='str', no_log=True),
|
||||
account_key_passphrase=dict(type='str', no_log=True),
|
||||
account_uri=dict(type='str'),
|
||||
acme_directory=dict(type='str'),
|
||||
acme_version=dict(type='int', choices=[1, 2]),
|
||||
acme_directory=dict(type='str', required=True),
|
||||
acme_version=dict(type='int', required=True, choices=[1, 2]),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
|
||||
request_timeout=dict(type='int', default=10),
|
||||
)
|
||||
|
||||
|
||||
def create_backend(module, needs_acme_v2):
|
||||
if not HAS_IPADDRESS:
|
||||
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
|
||||
# Backend autodetect
|
||||
@@ -345,8 +409,19 @@ def create_backend(module, needs_acme_v2):
|
||||
|
||||
# Create backend object
|
||||
if backend == 'cryptography':
|
||||
if CRYPTOGRAPHY_ERROR is not None:
|
||||
# Either we couldn't import cryptography at all, or there was an unexpected error
|
||||
if CRYPTOGRAPHY_VERSION is None:
|
||||
msg = missing_required_lib('cryptography')
|
||||
else:
|
||||
msg = 'Unexpected error while preparing cryptography: {0}'.format(CRYPTOGRAPHY_ERROR.splitlines()[-1])
|
||||
module.fail_json(msg=msg, exception=CRYPTOGRAPHY_ERROR)
|
||||
if not HAS_CURRENT_CRYPTOGRAPHY:
|
||||
module.fail_json(msg=missing_required_lib('cryptography'))
|
||||
# We succeeded importing cryptography, but its version is too old.
|
||||
module.fail_json(
|
||||
msg='Found cryptography, but only version {0}. {1}'.format(
|
||||
CRYPTOGRAPHY_VERSION,
|
||||
missing_required_lib('cryptography >= {0}'.format(CRYPTOGRAPHY_MINIMAL_VERSION))))
|
||||
module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION))
|
||||
module_backend = CryptographyBackend(module)
|
||||
elif backend == 'openssl':
|
||||
@@ -363,19 +438,13 @@ def create_backend(module, needs_acme_v2):
|
||||
'development purposes, but *never* for production purposes.'
|
||||
)
|
||||
|
||||
if module.params['acme_version'] is None:
|
||||
module.params['acme_version'] = 1
|
||||
module.deprecate("The option 'acme_version' will be required from community.crypto 2.0.0 on",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
if module.params['acme_directory'] is None:
|
||||
module.params['acme_directory'] = 'https://acme-staging.api.letsencrypt.org/directory'
|
||||
module.deprecate("The option 'acme_directory' will be required from community.crypto 2.0.0 on",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
if needs_acme_v2 and module.params['acme_version'] < 2:
|
||||
module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name))
|
||||
|
||||
if module.params['acme_version'] == 1:
|
||||
module.deprecate("The value 1 for 'acme_version' is deprecated. Please switch to ACME v2",
|
||||
version='3.0.0', collection_name='community.crypto')
|
||||
|
||||
# AnsibleModule() changes the locale, so change it back to C because we rely
|
||||
# on datetime.datetime.strptime() when parsing certificate dates.
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -13,6 +14,7 @@ import binascii
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
|
||||
@@ -47,6 +49,9 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
|
||||
extract_first_pem,
|
||||
)
|
||||
|
||||
CRYPTOGRAPHY_MINIMAL_VERSION = '1.5'
|
||||
|
||||
CRYPTOGRAPHY_ERROR = None
|
||||
try:
|
||||
import cryptography
|
||||
import cryptography.hazmat.backends
|
||||
@@ -59,13 +64,18 @@ try:
|
||||
import cryptography.hazmat.primitives.serialization
|
||||
import cryptography.x509
|
||||
import cryptography.x509.oid
|
||||
CRYPTOGRAPHY_VERSION = cryptography.__version__
|
||||
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5'))
|
||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
||||
except Exception as dummy:
|
||||
except ImportError as dummy:
|
||||
HAS_CURRENT_CRYPTOGRAPHY = False
|
||||
CRYPTOGRAPHY_VERSION = None
|
||||
CRYPTOGRAPHY_ERROR = traceback.format_exc()
|
||||
else:
|
||||
CRYPTOGRAPHY_VERSION = cryptography.__version__
|
||||
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion(CRYPTOGRAPHY_MINIMAL_VERSION))
|
||||
try:
|
||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
||||
except Exception as dummy:
|
||||
CRYPTOGRAPHY_ERROR = traceback.format_exc()
|
||||
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
@@ -123,11 +133,11 @@ class CryptographyChainMatcher(ChainMatcher):
|
||||
self.issuer = []
|
||||
if criterium.subject:
|
||||
self.subject = [
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject)
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject, 'subject')
|
||||
]
|
||||
if criterium.issuer:
|
||||
self.issuer = [
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer)
|
||||
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer, 'issuer')
|
||||
]
|
||||
self.subject_key_identifier = CryptographyChainMatcher._parse_key_identifier(
|
||||
criterium.subject_key_identifier, 'subject_key_identifier', criterium.index, module)
|
||||
@@ -192,7 +202,7 @@ class CryptographyBackend(CryptoBackend):
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns key_data.
|
||||
Raises KeyParsingError in case of errors.
|
||||
'''
|
||||
# If key_content isn't given, read key_file
|
||||
# If key_content is not given, read key_file
|
||||
if key_content is None:
|
||||
key_content = read_file(key_file)
|
||||
else:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -29,7 +30,10 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||
@@ -49,7 +53,7 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||
'''
|
||||
if passphrase is not None:
|
||||
raise KeyParsingError('openssl backend does not support key passphrases')
|
||||
# If key_file isn't given, but key_content, write that to a temporary file
|
||||
# If key_file is not given, but key_content, write that to a temporary file
|
||||
if key_file is None:
|
||||
fd, tmpsrc = tempfile.mkstemp()
|
||||
self.module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit
|
||||
@@ -216,9 +220,9 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||
@staticmethod
|
||||
def _normalize_ip(ip):
|
||||
try:
|
||||
return to_native(compat_ipaddress.ip_address(to_text(ip)).compressed)
|
||||
return to_native(ipaddress.ip_address(to_text(ip)).compressed)
|
||||
except ValueError:
|
||||
# We don't want to error out on something IPAddress() can't parse
|
||||
# We do not want to error out on something IPAddress() cannot parse
|
||||
return ip
|
||||
|
||||
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -16,8 +17,6 @@ import time
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||
nopad_b64,
|
||||
)
|
||||
@@ -28,6 +27,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
ModuleFailException,
|
||||
)
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def create_key_authorization(client, token):
|
||||
'''
|
||||
@@ -110,7 +114,7 @@ class Challenge(object):
|
||||
# https://www.rfc-editor.org/rfc/rfc8737.html#section-3
|
||||
if identifier_type == 'ip':
|
||||
# IPv4/IPv6 address: use reverse mapping (RFC1034, RFC3596)
|
||||
resource = compat_ipaddress.ip_address(identifier).reverse_pointer
|
||||
resource = ipaddress.ip_address(identifier).reverse_pointer
|
||||
if not resource.endswith('.'):
|
||||
resource += '.'
|
||||
else:
|
||||
@@ -297,3 +301,21 @@ class Authorization(object):
|
||||
self.status = 'deactivated'
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def wait_for_validation(authzs, client):
|
||||
'''
|
||||
Wait until a list of authz is valid. Fail if at least one of them is invalid or revoked.
|
||||
'''
|
||||
while authzs:
|
||||
authzs_next = []
|
||||
for authz in authzs:
|
||||
authz.refresh(client)
|
||||
if authz.status in ['valid', 'invalid', 'revoked']:
|
||||
if authz.status != 'valid':
|
||||
authz.raise_error('Status is not "valid"', module=client.module)
|
||||
else:
|
||||
authzs_next.append(authz)
|
||||
if authzs_next:
|
||||
time.sleep(2)
|
||||
authzs = authzs_next
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils.six import binary_type, PY3
|
||||
from ansible.module_utils.six.moves.http_client import responses as http_responses
|
||||
|
||||
|
||||
def format_http_status(status_code):
|
||||
expl = http_responses.get(status_code)
|
||||
if not expl:
|
||||
return str(status_code)
|
||||
return '%d %s' % (status_code, expl)
|
||||
|
||||
|
||||
def format_error_problem(problem, subproblem_prefix=''):
|
||||
@@ -86,9 +95,10 @@ class ACMEProtocolException(ModuleFailException):
|
||||
extras['http_status'] = code
|
||||
if code is not None and code >= 400 and content_json is not None and 'type' in content_json:
|
||||
if 'status' in content_json and content_json['status'] != code:
|
||||
code = 'status {problem_code} (HTTP status: {http_code})'.format(http_code=code, problem_code=content_json['status'])
|
||||
code = 'status {problem_code} (HTTP status: {http_code})'.format(
|
||||
http_code=format_http_status(code), problem_code=content_json['status'])
|
||||
else:
|
||||
code = 'status {problem_code}'.format(problem_code=code)
|
||||
code = 'status {problem_code}'.format(problem_code=format_http_status(code))
|
||||
subproblems = content_json.pop('subproblems', None)
|
||||
add_msg = ' {problem}.'.format(problem=format_error_problem(content_json))
|
||||
extras['problem'] = content_json
|
||||
@@ -102,12 +112,12 @@ class ACMEProtocolException(ModuleFailException):
|
||||
problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index)),
|
||||
)
|
||||
else:
|
||||
code = 'HTTP status {code}'.format(code=code)
|
||||
code = 'HTTP status {code}'.format(code=format_http_status(code))
|
||||
if content_json is not None:
|
||||
add_msg = ' The JSON error result: {content}'.format(content=content_json)
|
||||
elif content is not None:
|
||||
add_msg = ' The raw error result: {content}'.format(content=to_text(content))
|
||||
msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=code)
|
||||
msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=format_http_status(code))
|
||||
elif content_json is not None:
|
||||
add_msg = ' The JSON result: {content}'.format(content=content_json)
|
||||
elif content is not None:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2013, Romeo Theriault <romeot () hawaii.edu>
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2013, Romeo Theriault <romeot () hawaii.edu>
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# 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)
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
# THIS FILE IS FOR COMPATIBILITY ONLY! YOU SHALL NOT IMPORT IT!
|
||||
#
|
||||
# This fill will be removed eventually, so if you're using it,
|
||||
# please stop doing so.
|
||||
|
||||
from .basic import (
|
||||
HAS_PYOPENSSL,
|
||||
CRYPTOGRAPHY_HAS_X25519,
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||
CRYPTOGRAPHY_HAS_X448,
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
HAS_CRYPTOGRAPHY,
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from .cryptography_crl import (
|
||||
REVOCATION_REASON_MAP,
|
||||
REVOCATION_REASON_MAP_INVERSE,
|
||||
cryptography_decode_revoked_certificate,
|
||||
)
|
||||
|
||||
from .cryptography_support import (
|
||||
cryptography_get_extensions_from_cert,
|
||||
cryptography_get_extensions_from_csr,
|
||||
cryptography_name_to_oid,
|
||||
cryptography_oid_to_name,
|
||||
cryptography_get_name,
|
||||
cryptography_decode_name,
|
||||
cryptography_parse_key_usage_params,
|
||||
cryptography_get_basic_constraints,
|
||||
cryptography_key_needs_digest_for_signing,
|
||||
cryptography_compare_public_keys,
|
||||
)
|
||||
|
||||
from .pem import (
|
||||
identify_private_key_format,
|
||||
)
|
||||
|
||||
from .math import (
|
||||
binary_exp_mod,
|
||||
simple_gcd,
|
||||
quick_is_not_prime,
|
||||
count_bits,
|
||||
)
|
||||
|
||||
from ._obj2txt import obj2txt as _obj2txt
|
||||
|
||||
from ._objects_data import OID_MAP as _OID_MAP
|
||||
|
||||
from ._objects import OID_LOOKUP as _OID_LOOKUP
|
||||
from ._objects import NORMALIZE_NAMES as _NORMALIZE_NAMES
|
||||
from ._objects import NORMALIZE_NAMES_SHORT as _NORMALIZE_NAMES_SHORT
|
||||
|
||||
from .pyopenssl_support import (
|
||||
pyopenssl_normalize_name,
|
||||
pyopenssl_get_extensions_from_cert,
|
||||
pyopenssl_get_extensions_from_csr,
|
||||
)
|
||||
|
||||
from .support import (
|
||||
get_fingerprint_of_bytes,
|
||||
get_fingerprint,
|
||||
load_privatekey,
|
||||
load_certificate,
|
||||
load_certificate_request,
|
||||
parse_name_field,
|
||||
convert_relative_to_datetime,
|
||||
get_relative_time_option,
|
||||
select_message_digest,
|
||||
OpenSSLObject,
|
||||
)
|
||||
|
||||
from ..io import (
|
||||
load_file_if_exists,
|
||||
write_file,
|
||||
)
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2020, Jordan Borean <jborean93@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2020, Jordan Borean <jborean93@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is licensed under the
|
||||
# Apache 2.0 License. Modules you write using this snippet, which is embedded
|
||||
# dynamically by Ansible, still belong to the author of the module, and may assign
|
||||
# their own license to the complete work.
|
||||
|
||||
# This excerpt is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file at
|
||||
# https://github.com/pyca/cryptography/blob/master/LICENSE for complete details.
|
||||
#
|
||||
# The Apache 2.0 license has been included as Apache-2.0.txt in this collection.
|
||||
# The Apache 2.0 license has been included as LICENSES/Apache-2.0.txt in this collection.
|
||||
# The BSD License license has been included as LICENSES/BSD-3-Clause.txt in this collection.
|
||||
# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
#
|
||||
# Adapted from cryptography's hazmat/backends/openssl/decode_asn1.py
|
||||
#
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is licensed under the
|
||||
# Apache 2.0 License. Modules you write using this snippet, which is embedded
|
||||
# dynamically by Ansible, still belong to the author of the module, and may assign
|
||||
# their own license to the complete work.
|
||||
|
||||
# This has been extracted from the OpenSSL project's objects.txt:
|
||||
# https://github.com/openssl/openssl/blob/9537fe5757bb07761fa275d779bbd40bcf5530e4/crypto/objects/objects.txt
|
||||
# Extracted with https://gist.github.com/felixfontein/376748017ad65ead093d56a45a5bf376
|
||||
@@ -5,7 +11,8 @@
|
||||
# In case the following data structure has any copyrightable content, note that it is licensed as follows:
|
||||
# Copyright (c) the OpenSSL contributors
|
||||
# Licensed under the Apache License 2.0
|
||||
# https://github.com/openssl/openssl/blob/master/LICENSE.txt or Apache-2.0.txt
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# https://github.com/openssl/openssl/blob/master/LICENSE.txt or LICENSES/Apache-2.0.txt
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -22,14 +11,6 @@ __metaclass__ = type
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||
|
||||
try:
|
||||
import OpenSSL # noqa
|
||||
from OpenSSL import crypto # noqa
|
||||
HAS_PYOPENSSL = True
|
||||
except (ImportError, AttributeError):
|
||||
# Error handled in the calling module.
|
||||
HAS_PYOPENSSL = False
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -95,12 +84,12 @@ def cryptography_decode_revoked_certificate(cert):
|
||||
return result
|
||||
|
||||
|
||||
def cryptography_dump_revoked(entry):
|
||||
def cryptography_dump_revoked(entry, idn_rewrite='ignore'):
|
||||
return {
|
||||
'serial_number': entry['serial_number'],
|
||||
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
|
||||
'issuer':
|
||||
[cryptography_decode_name(issuer) for issuer in entry['issuer']]
|
||||
[cryptography_decode_name(issuer, idn_rewrite=idn_rewrite) for issuer in entry['issuer']]
|
||||
if entry['issuer'] is not None else None,
|
||||
'issuer_critical': entry['issuer_critical'],
|
||||
'reason': REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
|
||||
@@ -116,7 +105,7 @@ def cryptography_get_signature_algorithm_oid_from_crl(crl):
|
||||
try:
|
||||
return crl.signature_algorithm_oid
|
||||
except AttributeError:
|
||||
# Older cryptography versions don't have signature_algorithm_oid yet
|
||||
# Older cryptography versions do not have signature_algorithm_oid yet
|
||||
dotted = obj2txt(
|
||||
crl._backend._lib,
|
||||
crl._backend._ffi,
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -22,8 +11,12 @@ __metaclass__ = type
|
||||
import base64
|
||||
import binascii
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text, to_bytes, to_native
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlunparse, ParseResult
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text, to_bytes
|
||||
from ._asn1 import serialize_asn1_string_as_der
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||
@@ -79,6 +72,16 @@ except ImportError:
|
||||
# Error handled in the calling module.
|
||||
_load_pkcs12 = None
|
||||
|
||||
try:
|
||||
import idna
|
||||
|
||||
HAS_IDNA = True
|
||||
except ImportError:
|
||||
HAS_IDNA = False
|
||||
IDNA_IMP_ERROR = traceback.format_exc()
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
from .basic import (
|
||||
CRYPTOGRAPHY_HAS_DSA_SIGN,
|
||||
CRYPTOGRAPHY_HAS_EC_SIGN,
|
||||
@@ -87,6 +90,9 @@ from .basic import (
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
CRYPTOGRAPHY_HAS_ED448_SIGN,
|
||||
CRYPTOGRAPHY_HAS_RSA_SIGN,
|
||||
CRYPTOGRAPHY_HAS_X25519,
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||
CRYPTOGRAPHY_HAS_X448,
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
@@ -106,7 +112,7 @@ DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
|
||||
def cryptography_get_extensions_from_cert(cert):
|
||||
result = dict()
|
||||
try:
|
||||
# Since cryptography won't give us the DER value for an extension
|
||||
# Since cryptography will not give us the DER value for an extension
|
||||
# (that is only stored for unrecognized extensions), we have to re-do
|
||||
# the extension parsing outselves.
|
||||
backend = default_backend()
|
||||
@@ -132,7 +138,7 @@ def cryptography_get_extensions_from_cert(cert):
|
||||
der = backend._ffi.buffer(data.data, data.length)[:]
|
||||
entry = dict(
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der),
|
||||
value=to_native(base64.b64encode(der)),
|
||||
)
|
||||
try:
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
@@ -149,7 +155,7 @@ def cryptography_get_extensions_from_cert(cert):
|
||||
for ext in cert.extensions:
|
||||
result[ext.oid.dotted_string] = dict(
|
||||
critical=ext.critical,
|
||||
value=base64.b64encode(ext.value.public_bytes()),
|
||||
value=to_native(base64.b64encode(ext.value.public_bytes())),
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -158,7 +164,7 @@ def cryptography_get_extensions_from_cert(cert):
|
||||
def cryptography_get_extensions_from_csr(csr):
|
||||
result = dict()
|
||||
try:
|
||||
# Since cryptography won't give us the DER value for an extension
|
||||
# Since cryptography will not give us the DER value for an extension
|
||||
# (that is only stored for unrecognized extensions), we have to re-do
|
||||
# the extension parsing outselves.
|
||||
backend = default_backend()
|
||||
@@ -192,7 +198,7 @@ def cryptography_get_extensions_from_csr(csr):
|
||||
der = backend._ffi.buffer(data.data, data.length)[:]
|
||||
entry = dict(
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der),
|
||||
value=to_native(base64.b64encode(der)),
|
||||
)
|
||||
try:
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
@@ -209,7 +215,7 @@ def cryptography_get_extensions_from_csr(csr):
|
||||
for ext in csr.extensions:
|
||||
result[ext.oid.dotted_string] = dict(
|
||||
critical=ext.critical,
|
||||
value=base64.b64encode(ext.value.public_bytes()),
|
||||
value=to_native(base64.b64encode(ext.value.public_bytes())),
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -255,34 +261,68 @@ def _parse_hex(bytesstr):
|
||||
return data
|
||||
|
||||
|
||||
DN_COMPONENT_START_RE = re.compile(r'^ *([a-zA-z0-9]+) *= *')
|
||||
DN_COMPONENT_START_RE = re.compile(b'^ *([a-zA-z0-9.]+) *= *')
|
||||
DN_HEX_LETTER = b'0123456789abcdef'
|
||||
|
||||
|
||||
def _parse_dn_component(name, sep=',', sep_str='\\', decode_remainder=True):
|
||||
if sys.version_info[0] < 3:
|
||||
_int_to_byte = chr
|
||||
else:
|
||||
def _int_to_byte(value):
|
||||
return bytes((value, ))
|
||||
|
||||
|
||||
def _parse_dn_component(name, sep=b',', decode_remainder=True):
|
||||
m = DN_COMPONENT_START_RE.match(name)
|
||||
if not m:
|
||||
raise OpenSSLObjectError('cannot start part in "{0}"'.format(name))
|
||||
oid = cryptography_name_to_oid(m.group(1))
|
||||
raise OpenSSLObjectError(u'cannot start part in "{0}"'.format(to_text(name)))
|
||||
oid = cryptography_name_to_oid(to_text(m.group(1)))
|
||||
idx = len(m.group(0))
|
||||
decoded_name = []
|
||||
sep_str = sep + b'\\'
|
||||
if decode_remainder:
|
||||
length = len(name)
|
||||
while idx < length:
|
||||
i = idx
|
||||
while i < length and name[i] not in sep_str:
|
||||
i += 1
|
||||
if i > idx:
|
||||
decoded_name.append(name[idx:i])
|
||||
idx = i
|
||||
while idx + 1 < length and name[idx] == '\\':
|
||||
decoded_name.append(name[idx + 1])
|
||||
if length > idx and name[idx:idx + 1] == b'#':
|
||||
# Decoding a hex string
|
||||
idx += 1
|
||||
while idx + 1 < length:
|
||||
ch1 = name[idx:idx + 1]
|
||||
ch2 = name[idx + 1:idx + 2]
|
||||
idx1 = DN_HEX_LETTER.find(ch1.lower())
|
||||
idx2 = DN_HEX_LETTER.find(ch2.lower())
|
||||
if idx1 < 0 or idx2 < 0:
|
||||
raise OpenSSLObjectError(u'Invalid hex sequence entry "{0}"'.format(to_text(ch1 + ch2)))
|
||||
idx += 2
|
||||
if idx < length and name[idx] == sep:
|
||||
break
|
||||
decoded_name.append(_int_to_byte(idx1 * 16 + idx2))
|
||||
else:
|
||||
# Decoding a regular string
|
||||
while idx < length:
|
||||
i = idx
|
||||
while i < length and name[i:i + 1] not in sep_str:
|
||||
i += 1
|
||||
if i > idx:
|
||||
decoded_name.append(name[idx:i])
|
||||
idx = i
|
||||
while idx + 1 < length and name[idx:idx + 1] == b'\\':
|
||||
ch = name[idx + 1:idx + 2]
|
||||
idx1 = DN_HEX_LETTER.find(ch.lower())
|
||||
if idx1 >= 0:
|
||||
if idx + 2 >= length:
|
||||
raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" incomplete at end of string'.format(to_text(ch)))
|
||||
ch2 = name[idx + 2:idx + 3]
|
||||
idx2 = DN_HEX_LETTER.find(ch2.lower())
|
||||
if idx2 < 0:
|
||||
raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" has invalid second letter'.format(to_text(ch + ch2)))
|
||||
ch = _int_to_byte(idx1 * 16 + idx2)
|
||||
idx += 1
|
||||
idx += 2
|
||||
decoded_name.append(ch)
|
||||
if idx < length and name[idx:idx + 1] == sep:
|
||||
break
|
||||
else:
|
||||
decoded_name.append(name[idx:])
|
||||
idx = len(name)
|
||||
return x509.NameAttribute(oid, ''.join(decoded_name)), name[idx:]
|
||||
return x509.NameAttribute(oid, to_text(b''.join(decoded_name))), name[idx:]
|
||||
|
||||
|
||||
def _parse_dn(name):
|
||||
@@ -293,21 +333,20 @@ def _parse_dn(name):
|
||||
'''
|
||||
original_name = name
|
||||
name = name.lstrip()
|
||||
sep = ','
|
||||
if name.startswith('/'):
|
||||
sep = '/'
|
||||
sep = b','
|
||||
if name.startswith(b'/'):
|
||||
sep = b'/'
|
||||
name = name[1:]
|
||||
sep_str = sep + '\\'
|
||||
result = []
|
||||
while name:
|
||||
try:
|
||||
attribute, name = _parse_dn_component(name, sep=sep, sep_str=sep_str)
|
||||
attribute, name = _parse_dn_component(name, sep=sep)
|
||||
except OpenSSLObjectError as e:
|
||||
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": {1}'.format(original_name, e))
|
||||
raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": {1}'.format(to_text(original_name), e))
|
||||
result.append(attribute)
|
||||
if name:
|
||||
if name[0] != sep or len(name) < 2:
|
||||
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": unexpected end of string'.format(original_name))
|
||||
if name[0:1] != sep or len(name) < 2:
|
||||
raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": unexpected end of string'.format(to_text(original_name)))
|
||||
name = name[1:]
|
||||
return result
|
||||
|
||||
@@ -316,12 +355,86 @@ def cryptography_parse_relative_distinguished_name(rdn):
|
||||
names = []
|
||||
for part in rdn:
|
||||
try:
|
||||
names.append(_parse_dn_component(to_text(part), decode_remainder=False)[0])
|
||||
names.append(_parse_dn_component(to_bytes(part), decode_remainder=False)[0])
|
||||
except OpenSSLObjectError as e:
|
||||
raise OpenSSLObjectError('Error while parsing relative distinguished name "{0}": {1}'.format(part, e))
|
||||
raise OpenSSLObjectError(u'Error while parsing relative distinguished name "{0}": {1}'.format(part, e))
|
||||
return cryptography.x509.RelativeDistinguishedName(names)
|
||||
|
||||
|
||||
def _is_ascii(value):
|
||||
'''Check whether the Unicode string `value` contains only ASCII characters.'''
|
||||
try:
|
||||
value.encode("ascii")
|
||||
return True
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
|
||||
|
||||
def _adjust_idn(value, idn_rewrite):
|
||||
if idn_rewrite == 'ignore' or not value:
|
||||
return value
|
||||
if idn_rewrite == 'idna' and _is_ascii(value):
|
||||
return value
|
||||
if idn_rewrite not in ('idna', 'unicode'):
|
||||
raise ValueError('Invalid value for idn_rewrite: "{0}"'.format(idn_rewrite))
|
||||
if not HAS_IDNA:
|
||||
raise OpenSSLObjectError(
|
||||
missing_required_lib('idna', reason='to transform {what} DNS name "{name}" to {dest}'.format(
|
||||
name=value,
|
||||
what='IDNA' if idn_rewrite == 'unicode' else 'Unicode',
|
||||
dest='Unicode' if idn_rewrite == 'unicode' else 'IDNA',
|
||||
)))
|
||||
# Since IDNA does not like '*' or empty labels (except one empty label at the end),
|
||||
# we split and let IDNA only handle labels that are neither empty or '*'.
|
||||
parts = value.split(u'.')
|
||||
for index, part in enumerate(parts):
|
||||
if part in (u'', u'*'):
|
||||
continue
|
||||
try:
|
||||
if idn_rewrite == 'idna':
|
||||
parts[index] = idna.encode(part).decode('ascii')
|
||||
elif idn_rewrite == 'unicode' and part.startswith(u'xn--'):
|
||||
parts[index] = idna.decode(part)
|
||||
except idna.IDNAError as exc2008:
|
||||
try:
|
||||
if idn_rewrite == 'idna':
|
||||
parts[index] = part.encode('idna').decode('ascii')
|
||||
elif idn_rewrite == 'unicode' and part.startswith(u'xn--'):
|
||||
parts[index] = part.encode('ascii').decode('idna')
|
||||
except Exception as exc2003:
|
||||
raise OpenSSLObjectError(
|
||||
u'Error while transforming part "{part}" of {what} DNS name "{name}" to {dest}.'
|
||||
u' IDNA2008 transformation resulted in "{exc2008}", IDNA2003 transformation resulted in "{exc2003}".'.format(
|
||||
part=part,
|
||||
name=value,
|
||||
what='IDNA' if idn_rewrite == 'unicode' else 'Unicode',
|
||||
dest='Unicode' if idn_rewrite == 'unicode' else 'IDNA',
|
||||
exc2003=exc2003,
|
||||
exc2008=exc2008,
|
||||
))
|
||||
return u'.'.join(parts)
|
||||
|
||||
|
||||
def _adjust_idn_email(value, idn_rewrite):
|
||||
idx = value.find(u'@')
|
||||
if idx < 0:
|
||||
return value
|
||||
return u'{0}@{1}'.format(value[:idx], _adjust_idn(value[idx + 1:], idn_rewrite))
|
||||
|
||||
|
||||
def _adjust_idn_url(value, idn_rewrite):
|
||||
url = urlparse(value)
|
||||
host = _adjust_idn(url.hostname, idn_rewrite)
|
||||
if url.username is not None and url.password is not None:
|
||||
host = u'{0}:{1}@{2}'.format(url.username, url.password, host)
|
||||
elif url.username is not None:
|
||||
host = u'{0}@{1}'.format(url.username, host)
|
||||
if url.port is not None:
|
||||
host = u'{0}:{1}'.format(host, url.port)
|
||||
return urlunparse(
|
||||
ParseResult(scheme=url.scheme, netloc=host, path=url.path, params=url.params, query=url.query, fragment=url.fragment))
|
||||
|
||||
|
||||
def cryptography_get_name(name, what='Subject Alternative Name'):
|
||||
'''
|
||||
Given a name string, returns a cryptography x509.GeneralName object.
|
||||
@@ -329,16 +442,16 @@ def cryptography_get_name(name, what='Subject Alternative Name'):
|
||||
'''
|
||||
try:
|
||||
if name.startswith('DNS:'):
|
||||
return x509.DNSName(to_text(name[4:]))
|
||||
return x509.DNSName(_adjust_idn(to_text(name[4:]), 'idna'))
|
||||
if name.startswith('IP:'):
|
||||
address = to_text(name[3:])
|
||||
if '/' in address:
|
||||
return x509.IPAddress(ipaddress.ip_network(address))
|
||||
return x509.IPAddress(ipaddress.ip_address(address))
|
||||
if name.startswith('email:'):
|
||||
return x509.RFC822Name(to_text(name[6:]))
|
||||
return x509.RFC822Name(_adjust_idn_email(to_text(name[6:]), 'idna'))
|
||||
if name.startswith('URI:'):
|
||||
return x509.UniformResourceIdentifier(to_text(name[4:]))
|
||||
return x509.UniformResourceIdentifier(_adjust_idn_url(to_text(name[4:]), 'idna'))
|
||||
if name.startswith('RID:'):
|
||||
m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:]))
|
||||
if not m:
|
||||
@@ -362,7 +475,7 @@ def cryptography_get_name(name, what='Subject Alternative Name'):
|
||||
b_value = serialize_asn1_string_as_der(value)
|
||||
return x509.OtherName(x509.ObjectIdentifier(oid), b_value)
|
||||
if name.startswith('dirName:'):
|
||||
return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:]))))
|
||||
return x509.DirectoryName(x509.Name(reversed(_parse_dn(to_bytes(name[8:])))))
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError('Cannot parse {what} "{name}": {error}'.format(name=name, what=what, error=e))
|
||||
if ':' not in name:
|
||||
@@ -375,32 +488,39 @@ def _dn_escape_value(value):
|
||||
Escape Distinguished Name's attribute value.
|
||||
'''
|
||||
value = value.replace(u'\\', u'\\\\')
|
||||
for ch in [u',', u'#', u'+', u'<', u'>', u';', u'"', u'=', u'/']:
|
||||
for ch in [u',', u'+', u'<', u'>', u';', u'"']:
|
||||
value = value.replace(ch, u'\\%s' % ch)
|
||||
if value.startswith(u' '):
|
||||
value = u'\\ ' + value[1:]
|
||||
value = value.replace(u'\0', u'\\00')
|
||||
if value.startswith((u' ', u'#')):
|
||||
value = u'\\%s' % value[0] + value[1:]
|
||||
if value.endswith(u' '):
|
||||
value = value[:-1] + u'\\ '
|
||||
return value
|
||||
|
||||
|
||||
def cryptography_decode_name(name):
|
||||
def cryptography_decode_name(name, idn_rewrite='ignore'):
|
||||
'''
|
||||
Given a cryptography x509.GeneralName object, returns a string.
|
||||
Raises an OpenSSLObjectError if the name is not supported.
|
||||
'''
|
||||
if idn_rewrite not in ('ignore', 'idna', 'unicode'):
|
||||
raise AssertionError('idn_rewrite must be one of "ignore", "idna", or "unicode"')
|
||||
if isinstance(name, x509.DNSName):
|
||||
return u'DNS:{0}'.format(name.value)
|
||||
return u'DNS:{0}'.format(_adjust_idn(name.value, idn_rewrite))
|
||||
if isinstance(name, x509.IPAddress):
|
||||
if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
|
||||
return u'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
|
||||
return u'IP:{0}'.format(name.value.compressed)
|
||||
if isinstance(name, x509.RFC822Name):
|
||||
return u'email:{0}'.format(name.value)
|
||||
return u'email:{0}'.format(_adjust_idn_email(name.value, idn_rewrite))
|
||||
if isinstance(name, x509.UniformResourceIdentifier):
|
||||
return u'URI:{0}'.format(name.value)
|
||||
return u'URI:{0}'.format(_adjust_idn_url(name.value, idn_rewrite))
|
||||
if isinstance(name, x509.DirectoryName):
|
||||
return u'dirName:' + u''.join([
|
||||
u'/{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value))
|
||||
for attribute in name.value
|
||||
# According to https://datatracker.ietf.org/doc/html/rfc4514.html#section-2.1 the
|
||||
# list needs to be reversed, and joined by commas
|
||||
return u'dirName:' + ','.join([
|
||||
u'{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value))
|
||||
for attribute in reversed(list(name.value))
|
||||
])
|
||||
if isinstance(name, x509.RegisteredID):
|
||||
return u'RID:{0}'.format(name.value.dotted_string)
|
||||
@@ -495,32 +615,75 @@ def cryptography_key_needs_digest_for_signing(key):
|
||||
return True
|
||||
|
||||
|
||||
def _compare_public_keys(key1, key2, clazz):
|
||||
a = isinstance(key1, clazz)
|
||||
b = isinstance(key2, clazz)
|
||||
if not (a or b):
|
||||
return None
|
||||
if not a or not b:
|
||||
return False
|
||||
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
return a == b
|
||||
|
||||
|
||||
def cryptography_compare_public_keys(key1, key2):
|
||||
'''Tests whether two public keys are the same.
|
||||
|
||||
Needs special logic for Ed25519 and Ed448 keys, since they do not have public_numbers().
|
||||
'''
|
||||
if CRYPTOGRAPHY_HAS_ED25519:
|
||||
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
|
||||
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
|
||||
if a or b:
|
||||
if not a or not b:
|
||||
return False
|
||||
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
return a == b
|
||||
res = _compare_public_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
|
||||
if res is not None:
|
||||
return res
|
||||
if CRYPTOGRAPHY_HAS_ED448:
|
||||
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
|
||||
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
|
||||
if a or b:
|
||||
if not a or not b:
|
||||
return False
|
||||
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
return a == b
|
||||
res = _compare_public_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
|
||||
if res is not None:
|
||||
return res
|
||||
return key1.public_numbers() == key2.public_numbers()
|
||||
|
||||
|
||||
def _compare_private_keys(key1, key2, clazz, has_no_private_bytes=False):
|
||||
a = isinstance(key1, clazz)
|
||||
b = isinstance(key2, clazz)
|
||||
if not (a or b):
|
||||
return None
|
||||
if not a or not b:
|
||||
return False
|
||||
if has_no_private_bytes:
|
||||
# We do not have the private_bytes() function - compare associated public keys
|
||||
return cryptography_compare_public_keys(a.public_key(), b.public_key())
|
||||
encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption()
|
||||
a = key1.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, encryption_algorithm=encryption_algorithm)
|
||||
b = key2.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, encryption_algorithm=encryption_algorithm)
|
||||
return a == b
|
||||
|
||||
|
||||
def cryptography_compare_private_keys(key1, key2):
|
||||
'''Tests whether two private keys are the same.
|
||||
|
||||
Needs special logic for Ed25519, X25519, and Ed448 keys, since they do not have private_numbers().
|
||||
'''
|
||||
if CRYPTOGRAPHY_HAS_ED25519:
|
||||
res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey)
|
||||
if res is not None:
|
||||
return res
|
||||
if CRYPTOGRAPHY_HAS_X25519:
|
||||
res = _compare_private_keys(
|
||||
key1, key2, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey, has_no_private_bytes=not CRYPTOGRAPHY_HAS_X25519_FULL)
|
||||
if res is not None:
|
||||
return res
|
||||
if CRYPTOGRAPHY_HAS_ED448:
|
||||
res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey)
|
||||
if res is not None:
|
||||
return res
|
||||
if CRYPTOGRAPHY_HAS_X448:
|
||||
res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey)
|
||||
if res is not None:
|
||||
return res
|
||||
return key1.private_numbers() == key2.private_numbers()
|
||||
|
||||
|
||||
def cryptography_serial_number_of_cert(cert):
|
||||
'''Returns cert.serial_number.
|
||||
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -50,7 +39,7 @@ def quick_is_not_prime(n):
|
||||
'''Does some quick checks to see if we can poke a hole into the primality of n.
|
||||
|
||||
A result of `False` does **not** mean that the number is prime; it just means
|
||||
that we couldn't detect quickly whether it is not prime.
|
||||
that we could not detect quickly whether it is not prime.
|
||||
'''
|
||||
if n <= 2:
|
||||
return True
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -38,18 +39,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
)
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
CRYPTOGRAPHY_VERSION = None
|
||||
@@ -75,6 +64,7 @@ class CertificateBackend(object):
|
||||
self.backend = backend
|
||||
|
||||
self.force = module.params['force']
|
||||
self.ignore_timestamps = module.params['ignore_timestamps']
|
||||
self.privatekey_path = module.params['privatekey_path']
|
||||
self.privatekey_content = module.params['privatekey_content']
|
||||
if self.privatekey_content is not None:
|
||||
@@ -173,43 +163,12 @@ class CertificateBackend(object):
|
||||
|
||||
def _check_privatekey(self):
|
||||
"""Check whether provided parameters match, assuming self.existing_certificate and self.privatekey have been populated."""
|
||||
if self.backend == 'pyopenssl':
|
||||
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
|
||||
ctx.use_privatekey(self.privatekey)
|
||||
ctx.use_certificate(self.existing_certificate)
|
||||
try:
|
||||
ctx.check_privatekey()
|
||||
return True
|
||||
except OpenSSL.SSL.Error:
|
||||
return False
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key())
|
||||
|
||||
def _check_csr(self):
|
||||
"""Check whether provided parameters match, assuming self.existing_certificate and self.csr have been populated."""
|
||||
if self.backend == 'pyopenssl':
|
||||
# Verify that CSR is signed by certificate's private key
|
||||
try:
|
||||
self.csr.verify(self.existing_certificate.get_pubkey())
|
||||
except OpenSSL.crypto.Error:
|
||||
return False
|
||||
# Check subject
|
||||
if self.check_csr_subject and self.csr.get_subject() != self.existing_certificate.get_subject():
|
||||
return False
|
||||
# Check extensions
|
||||
if not self.check_csr_extensions:
|
||||
return True
|
||||
csr_extensions = self.csr.get_extensions()
|
||||
cert_extension_count = self.existing_certificate.get_extension_count()
|
||||
if len(csr_extensions) != cert_extension_count:
|
||||
return False
|
||||
for extension_number in range(0, cert_extension_count):
|
||||
cert_extension = self.existing_certificate.get_extension(extension_number)
|
||||
csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions)
|
||||
if cert_extension.get_data() != list(csr_extension)[0].get_data():
|
||||
return False
|
||||
return True
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
# Verify that CSR is signed by certificate's private key
|
||||
if not self.csr.is_signature_valid:
|
||||
return False
|
||||
@@ -244,10 +203,6 @@ class CertificateBackend(object):
|
||||
|
||||
def _check_subject_key_identifier(self):
|
||||
"""Check whether Subject Key Identifier matches, assuming self.existing_certificate has been populated."""
|
||||
if self.backend != 'cryptography':
|
||||
# We do not support SKI with pyOpenSSL backend
|
||||
return True
|
||||
|
||||
# Get hold of certificate's SKI
|
||||
try:
|
||||
ext = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
|
||||
@@ -265,12 +220,12 @@ class CertificateBackend(object):
|
||||
if ext.value.digest != x509.SubjectKeyIdentifier.from_public_key(self.existing_certificate.public_key()).digest:
|
||||
return False
|
||||
else:
|
||||
# If CSR had SKI and we didn't ignore it ('create_if_not_provided'), compare SKIs
|
||||
# If CSR had SKI and we did not ignore it ('create_if_not_provided'), compare SKIs
|
||||
if ext.value.digest != csr_ext.value.digest:
|
||||
return False
|
||||
return True
|
||||
|
||||
def needs_regeneration(self):
|
||||
def needs_regeneration(self, not_before=None, not_after=None):
|
||||
"""Check whether a regeneration is necessary."""
|
||||
if self.force or self.existing_certificate_bytes is None:
|
||||
return True
|
||||
@@ -294,6 +249,15 @@ class CertificateBackend(object):
|
||||
if self.create_subject_key_identifier != 'never_create' and not self._check_subject_key_identifier():
|
||||
return True
|
||||
|
||||
# Check not before
|
||||
if not_before is not None and not self.ignore_timestamps:
|
||||
if self.existing_certificate.not_valid_before != not_before:
|
||||
return True
|
||||
|
||||
# Check not after
|
||||
if not_after is not None and not self.ignore_timestamps:
|
||||
if self.existing_certificate.not_valid_after != not_after:
|
||||
return True
|
||||
return False
|
||||
|
||||
def dump(self, include_certificate):
|
||||
@@ -328,10 +292,6 @@ class CertificateProvider(object):
|
||||
def needs_version_two_certs(self, module):
|
||||
"""Whether the provider needs to create a version 2 certificate."""
|
||||
|
||||
def needs_pyopenssl_get_extensions(self, module):
|
||||
"""Whether the provider needs to use get_extensions() with pyOpenSSL."""
|
||||
return True
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_backend(self, module, backend):
|
||||
"""Create an implementation for a backend.
|
||||
@@ -352,45 +312,22 @@ def select_backend(module, backend, provider):
|
||||
if backend == 'auto':
|
||||
# Detect what backend we can use
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# If cryptography is available we'll use it
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
if provider.needs_version_two_certs(module):
|
||||
module.warn('crypto backend forced to pyopenssl. The cryptography library does not support v2 certificates')
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Fail if no backend has been found
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
module.fail_json(msg=("Cannot detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
if provider.needs_pyopenssl_get_extensions(module):
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
|
||||
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
if provider.needs_version_two_certs(module):
|
||||
module.fail_json(msg='The cryptography backend does not support v2 certificates, '
|
||||
'use select_crypto_backend=pyopenssl for v2 certificates')
|
||||
module.fail_json(msg='The cryptography backend does not support v2 certificates')
|
||||
|
||||
return provider.create_backend(module, backend)
|
||||
|
||||
@@ -402,7 +339,8 @@ def get_certificate_argument_spec():
|
||||
force=dict(type='bool', default=False,),
|
||||
csr_path=dict(type='path'),
|
||||
csr_content=dict(type='str'),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
ignore_timestamps=dict(type='bool', default=True),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
|
||||
# General properties of a certificate
|
||||
privatekey_path=dict(type='path'),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,664 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import abc
|
||||
import datetime
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
parse_name_field,
|
||||
get_relative_time_option,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_compare_public_keys,
|
||||
cryptography_get_name,
|
||||
cryptography_name_to_oid,
|
||||
cryptography_parse_key_usage_params,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_normalize_name_attribute,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
||||
CertificateBackend,
|
||||
CertificateProvider,
|
||||
)
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
from cryptography.x509 import NameAttribute, Name
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def compare_sets(subset, superset, equality=False):
|
||||
if equality:
|
||||
return set(subset) == set(superset)
|
||||
else:
|
||||
return all(x in superset for x in subset)
|
||||
|
||||
|
||||
def compare_dicts(subset, superset, equality=False):
|
||||
if equality:
|
||||
return subset == superset
|
||||
else:
|
||||
return all(superset.get(x) == v for x, v in subset.items())
|
||||
|
||||
|
||||
NO_EXTENSION = 'no extension'
|
||||
|
||||
|
||||
class AssertOnlyCertificateBackend(CertificateBackend):
|
||||
def __init__(self, module, backend):
|
||||
super(AssertOnlyCertificateBackend, self).__init__(module, backend)
|
||||
|
||||
self.signature_algorithms = module.params['signature_algorithms']
|
||||
if module.params['subject']:
|
||||
self.subject = parse_name_field(module.params['subject'])
|
||||
else:
|
||||
self.subject = []
|
||||
self.subject_strict = module.params['subject_strict']
|
||||
if module.params['issuer']:
|
||||
self.issuer = parse_name_field(module.params['issuer'])
|
||||
else:
|
||||
self.issuer = []
|
||||
self.issuer_strict = module.params['issuer_strict']
|
||||
self.has_expired = module.params['has_expired']
|
||||
self.version = module.params['version']
|
||||
self.key_usage = module.params['key_usage']
|
||||
self.key_usage_strict = module.params['key_usage_strict']
|
||||
self.extended_key_usage = module.params['extended_key_usage']
|
||||
self.extended_key_usage_strict = module.params['extended_key_usage_strict']
|
||||
self.subject_alt_name = module.params['subject_alt_name']
|
||||
self.subject_alt_name_strict = module.params['subject_alt_name_strict']
|
||||
self.not_before = module.params['not_before']
|
||||
self.not_after = module.params['not_after']
|
||||
self.valid_at = module.params['valid_at']
|
||||
self.invalid_at = module.params['invalid_at']
|
||||
self.valid_in = module.params['valid_in']
|
||||
if self.valid_in and not self.valid_in.startswith("+") and not self.valid_in.startswith("-"):
|
||||
try:
|
||||
int(self.valid_in)
|
||||
except ValueError:
|
||||
module.fail_json(msg='The supplied value for "valid_in" (%s) is not an integer or a valid timespec' % self.valid_in)
|
||||
self.valid_in = "+" + self.valid_in + "s"
|
||||
|
||||
# Load objects
|
||||
self._ensure_private_key_loaded()
|
||||
self._ensure_csr_loaded()
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_privatekey(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_csr_signature(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_csr_subject(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_csr_extensions(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_signature_algorithms(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_subject(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_issuer(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_has_expired(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_version(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_extended_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_subject_alt_name(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_not_before(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_not_after(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_valid_at(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_invalid_at(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _validate_valid_in(self):
|
||||
pass
|
||||
|
||||
def assertonly(self):
|
||||
messages = []
|
||||
if self.privatekey_path is not None or self.privatekey_content is not None:
|
||||
if not self._validate_privatekey():
|
||||
messages.append(
|
||||
'Certificate and private key %s do not match' %
|
||||
(self.privatekey_path or '(provided in module options)')
|
||||
)
|
||||
|
||||
if self.csr_path is not None or self.csr_content is not None:
|
||||
if not self._validate_csr_signature():
|
||||
messages.append(
|
||||
'Certificate and CSR %s do not match: private key mismatch' %
|
||||
(self.csr_path or '(provided in module options)')
|
||||
)
|
||||
if not self._validate_csr_subject():
|
||||
messages.append(
|
||||
'Certificate and CSR %s do not match: subject mismatch' %
|
||||
(self.csr_path or '(provided in module options)')
|
||||
)
|
||||
if not self._validate_csr_extensions():
|
||||
messages.append(
|
||||
'Certificate and CSR %s do not match: extensions mismatch' %
|
||||
(self.csr_path or '(provided in module options)')
|
||||
)
|
||||
|
||||
if self.signature_algorithms is not None:
|
||||
wrong_alg = self._validate_signature_algorithms()
|
||||
if wrong_alg:
|
||||
messages.append(
|
||||
'Invalid signature algorithm (got %s, expected one of %s)' %
|
||||
(wrong_alg, self.signature_algorithms)
|
||||
)
|
||||
|
||||
if self.subject is not None:
|
||||
failure = self._validate_subject()
|
||||
if failure:
|
||||
dummy, cert_subject = failure
|
||||
messages.append(
|
||||
'Invalid subject component (got %s, expected all of %s to be present)' %
|
||||
(cert_subject, self.subject)
|
||||
)
|
||||
|
||||
if self.issuer is not None:
|
||||
failure = self._validate_issuer()
|
||||
if failure:
|
||||
dummy, cert_issuer = failure
|
||||
messages.append(
|
||||
'Invalid issuer component (got %s, expected all of %s to be present)' % (cert_issuer, self.issuer)
|
||||
)
|
||||
|
||||
if self.has_expired is not None:
|
||||
cert_expired = self._validate_has_expired()
|
||||
if cert_expired != self.has_expired:
|
||||
messages.append(
|
||||
'Certificate expiration check failed (certificate expiration is %s, expected %s)' %
|
||||
(cert_expired, self.has_expired)
|
||||
)
|
||||
|
||||
if self.version is not None:
|
||||
cert_version = self._validate_version()
|
||||
if cert_version != self.version:
|
||||
messages.append(
|
||||
'Invalid certificate version number (got %s, expected %s)' %
|
||||
(cert_version, self.version)
|
||||
)
|
||||
|
||||
if self.key_usage is not None:
|
||||
failure = self._validate_key_usage()
|
||||
if failure == NO_EXTENSION:
|
||||
messages.append('Found no keyUsage extension')
|
||||
elif failure:
|
||||
dummy, cert_key_usage = failure
|
||||
messages.append(
|
||||
'Invalid keyUsage components (got %s, expected all of %s to be present)' %
|
||||
(cert_key_usage, self.key_usage)
|
||||
)
|
||||
|
||||
if self.extended_key_usage is not None:
|
||||
failure = self._validate_extended_key_usage()
|
||||
if failure == NO_EXTENSION:
|
||||
messages.append('Found no extendedKeyUsage extension')
|
||||
elif failure:
|
||||
dummy, ext_cert_key_usage = failure
|
||||
messages.append(
|
||||
'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % (ext_cert_key_usage, self.extended_key_usage)
|
||||
)
|
||||
|
||||
if self.subject_alt_name is not None:
|
||||
failure = self._validate_subject_alt_name()
|
||||
if failure == NO_EXTENSION:
|
||||
messages.append('Found no subjectAltName extension')
|
||||
elif failure:
|
||||
dummy, cert_san = failure
|
||||
messages.append(
|
||||
'Invalid subjectAltName component (got %s, expected all of %s to be present)' %
|
||||
(cert_san, self.subject_alt_name)
|
||||
)
|
||||
|
||||
if self.not_before is not None:
|
||||
cert_not_valid_before = self._validate_not_before()
|
||||
if cert_not_valid_before != get_relative_time_option(self.not_before, 'not_before', backend=self.backend):
|
||||
messages.append(
|
||||
'Invalid not_before component (got %s, expected %s to be present)' %
|
||||
(cert_not_valid_before, self.not_before)
|
||||
)
|
||||
|
||||
if self.not_after is not None:
|
||||
cert_not_valid_after = self._validate_not_after()
|
||||
if cert_not_valid_after != get_relative_time_option(self.not_after, 'not_after', backend=self.backend):
|
||||
messages.append(
|
||||
'Invalid not_after component (got %s, expected %s to be present)' %
|
||||
(cert_not_valid_after, self.not_after)
|
||||
)
|
||||
|
||||
if self.valid_at is not None:
|
||||
not_before, valid_at, not_after = self._validate_valid_at()
|
||||
if not (not_before <= valid_at <= not_after):
|
||||
messages.append(
|
||||
'Certificate is not valid for the specified date (%s) - not_before: %s - not_after: %s' %
|
||||
(self.valid_at, not_before, not_after)
|
||||
)
|
||||
|
||||
if self.invalid_at is not None:
|
||||
not_before, invalid_at, not_after = self._validate_invalid_at()
|
||||
if not_before <= invalid_at <= not_after:
|
||||
messages.append(
|
||||
'Certificate is not invalid for the specified date (%s) - not_before: %s - not_after: %s' %
|
||||
(self.invalid_at, not_before, not_after)
|
||||
)
|
||||
|
||||
if self.valid_in is not None:
|
||||
not_before, valid_in, not_after = self._validate_valid_in()
|
||||
if not not_before <= valid_in <= not_after:
|
||||
messages.append(
|
||||
'Certificate is not valid in %s from now (that would be %s) - not_before: %s - not_after: %s' %
|
||||
(self.valid_in, valid_in, not_before, not_after)
|
||||
)
|
||||
return messages
|
||||
|
||||
def needs_regeneration(self):
|
||||
self._ensure_existing_certificate_loaded()
|
||||
if self.existing_certificate is None:
|
||||
self.messages = ['Certificate not provided']
|
||||
else:
|
||||
self.messages = self.assertonly()
|
||||
|
||||
return len(self.messages) != 0
|
||||
|
||||
def generate_certificate(self):
|
||||
self.module.fail_json(msg=' | '.join(self.messages))
|
||||
|
||||
def get_certificate_data(self):
|
||||
return self.existing_certificate_bytes
|
||||
|
||||
|
||||
class AssertOnlyCertificateBackendCryptography(AssertOnlyCertificateBackend):
|
||||
"""Validate the supplied cert, using the cryptography backend"""
|
||||
def __init__(self, module):
|
||||
super(AssertOnlyCertificateBackendCryptography, self).__init__(module, 'cryptography')
|
||||
|
||||
def _validate_privatekey(self):
|
||||
return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key())
|
||||
|
||||
def _validate_csr_signature(self):
|
||||
if not self.csr.is_signature_valid:
|
||||
return False
|
||||
return cryptography_compare_public_keys(self.csr.public_key(), self.existing_certificate.public_key())
|
||||
|
||||
def _validate_csr_subject(self):
|
||||
return self.csr.subject == self.existing_certificate.subject
|
||||
|
||||
def _validate_csr_extensions(self):
|
||||
cert_exts = self.existing_certificate.extensions
|
||||
csr_exts = self.csr.extensions
|
||||
if len(cert_exts) != len(csr_exts):
|
||||
return False
|
||||
for cert_ext in cert_exts:
|
||||
try:
|
||||
csr_ext = csr_exts.get_extension_for_oid(cert_ext.oid)
|
||||
if cert_ext != csr_ext:
|
||||
return False
|
||||
except cryptography.x509.ExtensionNotFound as dummy:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_signature_algorithms(self):
|
||||
if self.existing_certificate.signature_algorithm_oid._name not in self.signature_algorithms:
|
||||
return self.existing_certificate.signature_algorithm_oid._name
|
||||
|
||||
def _validate_subject(self):
|
||||
expected_subject = Name([NameAttribute(oid=cryptography_name_to_oid(sub[0]), value=to_text(sub[1]))
|
||||
for sub in self.subject])
|
||||
cert_subject = self.existing_certificate.subject
|
||||
if not compare_sets(expected_subject, cert_subject, self.subject_strict):
|
||||
return expected_subject, cert_subject
|
||||
|
||||
def _validate_issuer(self):
|
||||
expected_issuer = Name([NameAttribute(oid=cryptography_name_to_oid(iss[0]), value=to_text(iss[1]))
|
||||
for iss in self.issuer])
|
||||
cert_issuer = self.existing_certificate.issuer
|
||||
if not compare_sets(expected_issuer, cert_issuer, self.issuer_strict):
|
||||
return self.issuer, cert_issuer
|
||||
|
||||
def _validate_has_expired(self):
|
||||
cert_not_after = self.existing_certificate.not_valid_after
|
||||
cert_expired = cert_not_after < datetime.datetime.utcnow()
|
||||
return cert_expired
|
||||
|
||||
def _validate_version(self):
|
||||
if self.existing_certificate.version == x509.Version.v1:
|
||||
return 1
|
||||
if self.existing_certificate.version == x509.Version.v3:
|
||||
return 3
|
||||
return "unknown"
|
||||
|
||||
def _validate_key_usage(self):
|
||||
try:
|
||||
current_key_usage = self.existing_certificate.extensions.get_extension_for_class(x509.KeyUsage).value
|
||||
test_key_usage = dict(
|
||||
digital_signature=current_key_usage.digital_signature,
|
||||
content_commitment=current_key_usage.content_commitment,
|
||||
key_encipherment=current_key_usage.key_encipherment,
|
||||
data_encipherment=current_key_usage.data_encipherment,
|
||||
key_agreement=current_key_usage.key_agreement,
|
||||
key_cert_sign=current_key_usage.key_cert_sign,
|
||||
crl_sign=current_key_usage.crl_sign,
|
||||
encipher_only=False,
|
||||
decipher_only=False
|
||||
)
|
||||
if test_key_usage['key_agreement']:
|
||||
test_key_usage.update(dict(
|
||||
encipher_only=current_key_usage.encipher_only,
|
||||
decipher_only=current_key_usage.decipher_only
|
||||
))
|
||||
|
||||
key_usages = cryptography_parse_key_usage_params(self.key_usage)
|
||||
if not compare_dicts(key_usages, test_key_usage, self.key_usage_strict):
|
||||
return self.key_usage, [k for k, v in test_key_usage.items() if v is True]
|
||||
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_extended_key_usage(self):
|
||||
try:
|
||||
current_ext_keyusage = self.existing_certificate.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value
|
||||
usages = [cryptography_name_to_oid(usage) for usage in self.extended_key_usage]
|
||||
expected_ext_keyusage = x509.ExtendedKeyUsage(usages)
|
||||
if not compare_sets(expected_ext_keyusage, current_ext_keyusage, self.extended_key_usage_strict):
|
||||
return [eku.value for eku in expected_ext_keyusage], [eku.value for eku in current_ext_keyusage]
|
||||
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.extended_key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_subject_alt_name(self):
|
||||
try:
|
||||
current_san = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
|
||||
expected_san = [cryptography_get_name(san) for san in self.subject_alt_name]
|
||||
if not compare_sets(expected_san, current_san, self.subject_alt_name_strict):
|
||||
return self.subject_alt_name, current_san
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.subject_alt_name:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_not_before(self):
|
||||
return self.existing_certificate.not_valid_before
|
||||
|
||||
def _validate_not_after(self):
|
||||
return self.existing_certificate.not_valid_after
|
||||
|
||||
def _validate_valid_at(self):
|
||||
rt = get_relative_time_option(self.valid_at, 'valid_at', backend=self.backend)
|
||||
return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after
|
||||
|
||||
def _validate_invalid_at(self):
|
||||
rt = get_relative_time_option(self.invalid_at, 'invalid_at', backend=self.backend)
|
||||
return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after
|
||||
|
||||
def _validate_valid_in(self):
|
||||
valid_in_date = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend)
|
||||
return self.existing_certificate.not_valid_before, valid_in_date, self.existing_certificate.not_valid_after
|
||||
|
||||
|
||||
class AssertOnlyCertificateBackendPyOpenSSL(AssertOnlyCertificateBackend):
|
||||
"""validate the supplied certificate."""
|
||||
|
||||
def __init__(self, module):
|
||||
super(AssertOnlyCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
# Ensure inputs are properly sanitized before comparison.
|
||||
for param in ['signature_algorithms', 'key_usage', 'extended_key_usage',
|
||||
'subject_alt_name', 'subject', 'issuer', 'not_before',
|
||||
'not_after', 'valid_at', 'invalid_at']:
|
||||
attr = getattr(self, param)
|
||||
if isinstance(attr, list) and attr:
|
||||
if isinstance(attr[0], str):
|
||||
setattr(self, param, [to_bytes(item) for item in attr])
|
||||
elif isinstance(attr[0], tuple):
|
||||
setattr(self, param, [(to_bytes(item[0]), to_bytes(item[1])) for item in attr])
|
||||
elif isinstance(attr, tuple):
|
||||
setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items()))
|
||||
elif isinstance(attr, dict):
|
||||
setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items()))
|
||||
elif isinstance(attr, str):
|
||||
setattr(self, param, to_bytes(attr))
|
||||
|
||||
def _validate_privatekey(self):
|
||||
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
|
||||
ctx.use_privatekey(self.privatekey)
|
||||
ctx.use_certificate(self.existing_certificate)
|
||||
try:
|
||||
ctx.check_privatekey()
|
||||
return True
|
||||
except OpenSSL.SSL.Error:
|
||||
return False
|
||||
|
||||
def _validate_csr_signature(self):
|
||||
try:
|
||||
self.csr.verify(self.existing_certificate.get_pubkey())
|
||||
except OpenSSL.crypto.Error:
|
||||
return False
|
||||
|
||||
def _validate_csr_subject(self):
|
||||
if self.csr.get_subject() != self.existing_certificate.get_subject():
|
||||
return False
|
||||
|
||||
def _validate_csr_extensions(self):
|
||||
csr_extensions = self.csr.get_extensions()
|
||||
cert_extension_count = self.existing_certificate.get_extension_count()
|
||||
if len(csr_extensions) != cert_extension_count:
|
||||
return False
|
||||
for extension_number in range(0, cert_extension_count):
|
||||
cert_extension = self.existing_certificate.get_extension(extension_number)
|
||||
csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions)
|
||||
if cert_extension.get_data() != list(csr_extension)[0].get_data():
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_signature_algorithms(self):
|
||||
if self.existing_certificate.get_signature_algorithm() not in self.signature_algorithms:
|
||||
return self.existing_certificate.get_signature_algorithm()
|
||||
|
||||
def _validate_subject(self):
|
||||
expected_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in self.subject]
|
||||
cert_subject = self.existing_certificate.get_subject().get_components()
|
||||
current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in cert_subject]
|
||||
if not compare_sets(expected_subject, current_subject, self.subject_strict):
|
||||
return expected_subject, current_subject
|
||||
|
||||
def _validate_issuer(self):
|
||||
expected_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in self.issuer]
|
||||
cert_issuer = self.existing_certificate.get_issuer().get_components()
|
||||
current_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in cert_issuer]
|
||||
if not compare_sets(expected_issuer, current_issuer, self.issuer_strict):
|
||||
return self.issuer, cert_issuer
|
||||
|
||||
def _validate_has_expired(self):
|
||||
# The following 3 lines are the same as the current PyOpenSSL code for cert.has_expired().
|
||||
# Older version of PyOpenSSL have a buggy implementation,
|
||||
# to avoid issues with those we added the code from a more recent release here.
|
||||
|
||||
time_string = to_native(self.existing_certificate.get_notAfter())
|
||||
not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
cert_expired = not_after < datetime.datetime.utcnow()
|
||||
return cert_expired
|
||||
|
||||
def _validate_version(self):
|
||||
# Version numbers in certs are off by one:
|
||||
# v1: 0, v2: 1, v3: 2 ...
|
||||
return self.existing_certificate.get_version() + 1
|
||||
|
||||
def _validate_key_usage(self):
|
||||
found = False
|
||||
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
|
||||
extension = self.existing_certificate.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'keyUsage':
|
||||
found = True
|
||||
expected_extension = crypto.X509Extension(b"keyUsage", False, b', '.join(self.key_usage))
|
||||
key_usage = [usage.strip() for usage in to_text(expected_extension, errors='surrogate_or_strict').split(',')]
|
||||
current_ku = [usage.strip() for usage in to_text(extension, errors='surrogate_or_strict').split(',')]
|
||||
if not compare_sets(key_usage, current_ku, self.key_usage_strict):
|
||||
return self.key_usage, str(extension).split(', ')
|
||||
if not found:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_extended_key_usage(self):
|
||||
found = False
|
||||
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
|
||||
extension = self.existing_certificate.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'extendedKeyUsage':
|
||||
found = True
|
||||
extKeyUsage = [OpenSSL._util.lib.OBJ_txt2nid(keyUsage) for keyUsage in self.extended_key_usage]
|
||||
current_xku = [OpenSSL._util.lib.OBJ_txt2nid(usage.strip()) for usage in
|
||||
to_bytes(extension, errors='surrogate_or_strict').split(b',')]
|
||||
if not compare_sets(extKeyUsage, current_xku, self.extended_key_usage_strict):
|
||||
return self.extended_key_usage, str(extension).split(', ')
|
||||
if not found:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.extended_key_usage:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_subject_alt_name(self):
|
||||
found = False
|
||||
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
|
||||
extension = self.existing_certificate.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
found = True
|
||||
l_altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
sans = [pyopenssl_normalize_name_attribute(to_text(san, errors='surrogate_or_strict')) for san in self.subject_alt_name]
|
||||
if not compare_sets(sans, l_altnames, self.subject_alt_name_strict):
|
||||
return self.subject_alt_name, l_altnames
|
||||
if not found:
|
||||
# This is only bad if the user specified a non-empty list
|
||||
if self.subject_alt_name:
|
||||
return NO_EXTENSION
|
||||
|
||||
def _validate_not_before(self):
|
||||
return self.existing_certificate.get_notBefore()
|
||||
|
||||
def _validate_not_after(self):
|
||||
return self.existing_certificate.get_notAfter()
|
||||
|
||||
def _validate_valid_at(self):
|
||||
rt = get_relative_time_option(self.valid_at, "valid_at", backend=self.backend)
|
||||
rt = to_bytes(rt, errors='surrogate_or_strict')
|
||||
return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter()
|
||||
|
||||
def _validate_invalid_at(self):
|
||||
rt = get_relative_time_option(self.invalid_at, "invalid_at", backend=self.backend)
|
||||
rt = to_bytes(rt, errors='surrogate_or_strict')
|
||||
return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter()
|
||||
|
||||
def _validate_valid_in(self):
|
||||
valid_in_asn1 = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend)
|
||||
valid_in_date = to_bytes(valid_in_asn1, errors='surrogate_or_strict')
|
||||
return self.existing_certificate.get_notBefore(), valid_in_date, self.existing_certificate.get_notAfter()
|
||||
|
||||
|
||||
class AssertOnlyCertificateProvider(CertificateProvider):
|
||||
def validate_module_args(self, module):
|
||||
module.deprecate("The 'assertonly' provider is deprecated; please see the examples of "
|
||||
"the 'x509_certificate' module on how to replace it with other modules",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
def needs_version_two_certs(self, module):
|
||||
return False
|
||||
|
||||
def create_backend(self, module, backend):
|
||||
if backend == 'cryptography':
|
||||
return AssertOnlyCertificateBackendCryptography(module)
|
||||
if backend == 'pyopenssl':
|
||||
return AssertOnlyCertificateBackendPyOpenSSL(module)
|
||||
|
||||
|
||||
def add_assertonly_provider_to_argument_spec(argument_spec):
|
||||
argument_spec.argument_spec['provider']['choices'].append('assertonly')
|
||||
argument_spec.argument_spec.update(dict(
|
||||
signature_algorithms=dict(type='list', elements='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
issuer=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
issuer_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
has_expired=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
version=dict(type='int', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
key_usage=dict(type='list', elements='str', aliases=['keyUsage'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
key_usage_strict=dict(type='bool', default=False, aliases=['keyUsage_strict'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
extended_key_usage=dict(type='list', elements='str', aliases=['extendedKeyUsage'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
extended_key_usage_strict=dict(type='bool', default=False, aliases=['extendedKeyUsage_strict'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject_alt_name=dict(type='list', elements='str', aliases=['subjectAltName'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
subject_alt_name_strict=dict(type='bool', default=False, aliases=['subjectAltName_strict'],
|
||||
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
not_before=dict(type='str', aliases=['notBefore'], removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
not_after=dict(type='str', aliases=['notAfter'], removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
valid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
invalid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
valid_in=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
|
||||
))
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -58,18 +59,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
# We want to always force behavior of trying to use the organization provided in the CSR.
|
||||
# To that end we need to parse out the organization from the CSR.
|
||||
self.csr_org = None
|
||||
if self.backend == 'pyopenssl':
|
||||
csr_subject = self.csr.get_subject()
|
||||
csr_subject_components = csr_subject.get_components()
|
||||
for k, v in csr_subject_components:
|
||||
if k.upper() == 'O':
|
||||
# Entrust does not support multiple validated organizations in a single certificate
|
||||
if self.csr_org is not None:
|
||||
self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations "
|
||||
"found in Subject DN: '{0}'. ".format(csr_subject)))
|
||||
else:
|
||||
self.csr_org = v
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
csr_subject_orgs = self.csr.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)
|
||||
if len(csr_subject_orgs) == 1:
|
||||
self.csr_org = csr_subject_orgs[0].value
|
||||
@@ -162,11 +152,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
if self.existing_certificate:
|
||||
serial_number = None
|
||||
expiry = None
|
||||
if self.backend == 'pyopenssl':
|
||||
serial_number = "{0:X}".format(self.existing_certificate.get_serial_number())
|
||||
time_string = to_native(self.existing_certificate.get_notAfter())
|
||||
expiry = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
elif self.backend == 'cryptography':
|
||||
if self.backend == 'cryptography':
|
||||
serial_number = "{0:X}".format(cryptography_serial_number_of_cert(self.existing_certificate))
|
||||
expiry = self.existing_certificate.not_valid_after
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -12,12 +13,11 @@ __metaclass__ = type
|
||||
import abc
|
||||
import binascii
|
||||
import datetime
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils import six
|
||||
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
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||
|
||||
@@ -33,37 +33,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
cryptography_serial_number_of_cert,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_get_extensions_from_cert,
|
||||
pyopenssl_normalize_name,
|
||||
pyopenssl_normalize_name_attribute,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
get_publickey_info,
|
||||
)
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# OpenSSL 1.1.0 or newer
|
||||
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
|
||||
else:
|
||||
# OpenSSL 1.0.x or older
|
||||
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -165,9 +139,13 @@ class CertificateInfoRetrieval(object):
|
||||
def _get_ocsp_uri(self):
|
||||
pass
|
||||
|
||||
def get_info(self, prefer_one_fingerprint=False):
|
||||
@abc.abstractmethod
|
||||
def _get_issuer_uri(self):
|
||||
pass
|
||||
|
||||
def get_info(self, prefer_one_fingerprint=False, der_support_enabled=False):
|
||||
result = dict()
|
||||
self.cert = load_certificate(None, content=self.content, backend=self.backend)
|
||||
self.cert = load_certificate(None, content=self.content, backend=self.backend, der_support_enabled=der_support_enabled)
|
||||
|
||||
result['signature_algorithm'] = self._get_signature_algorithm()
|
||||
subject = self._get_subject_ordered()
|
||||
@@ -193,7 +171,7 @@ class CertificateInfoRetrieval(object):
|
||||
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
|
||||
result['expired'] = not_after < datetime.datetime.utcnow()
|
||||
|
||||
result['public_key'] = self._get_public_key_pem()
|
||||
result['public_key'] = to_native(self._get_public_key_pem())
|
||||
|
||||
public_key_info = get_publickey_info(
|
||||
self.module,
|
||||
@@ -209,24 +187,24 @@ class CertificateInfoRetrieval(object):
|
||||
result['fingerprints'] = get_fingerprint_of_bytes(
|
||||
self._get_der_bytes(), prefer_one=prefer_one_fingerprint)
|
||||
|
||||
if self.backend != 'pyopenssl':
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
|
||||
result['serial_number'] = self._get_serial_number()
|
||||
result['extensions_by_oid'] = self._get_all_extensions()
|
||||
result['ocsp_uri'] = self._get_ocsp_uri()
|
||||
result['issuer_uri'] = self._get_issuer_uri()
|
||||
|
||||
return result
|
||||
|
||||
@@ -235,6 +213,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
"""Validate the supplied cert, using the cryptography backend"""
|
||||
def __init__(self, module, content):
|
||||
super(CertificateInfoRetrievalCryptography, self).__init__(module, 'cryptography', content)
|
||||
self.name_encoding = module.params.get('name_encoding', 'ignore')
|
||||
|
||||
def _get_der_bytes(self):
|
||||
return self.cert.public_bytes(serialization.Encoding.DER)
|
||||
@@ -337,7 +316,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
def _get_subject_alt_name(self):
|
||||
try:
|
||||
san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
result = [cryptography_decode_name(san) for san in san_ext.value]
|
||||
result = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in san_ext.value]
|
||||
return result, san_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
@@ -369,7 +348,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
|
||||
issuer = None
|
||||
if ext.value.authority_cert_issuer is not None:
|
||||
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
|
||||
issuer = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in ext.value.authority_cert_issuer]
|
||||
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, None, None
|
||||
@@ -391,137 +370,21 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class CertificateInfoRetrievalPyOpenSSL(CertificateInfoRetrieval):
|
||||
"""validate the supplied certificate."""
|
||||
|
||||
def __init__(self, module, content):
|
||||
super(CertificateInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content)
|
||||
|
||||
def _get_der_bytes(self):
|
||||
return crypto.dump_certificate(crypto.FILETYPE_ASN1, self.cert)
|
||||
|
||||
def _get_signature_algorithm(self):
|
||||
return to_text(self.cert.get_signature_algorithm())
|
||||
|
||||
def __get_name(self, name):
|
||||
result = []
|
||||
for sub in name.get_components():
|
||||
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
|
||||
return result
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
return self.__get_name(self.cert.get_subject())
|
||||
|
||||
def _get_issuer_ordered(self):
|
||||
return self.__get_name(self.cert.get_issuer())
|
||||
|
||||
def _get_version(self):
|
||||
# Version numbers in certs are off by one:
|
||||
# v1: 0, v2: 1, v3: 2 ...
|
||||
return self.cert.get_version() + 1
|
||||
|
||||
def _get_extension(self, short_name):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == short_name:
|
||||
result = [
|
||||
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
|
||||
]
|
||||
return sorted(result), bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_key_usage(self):
|
||||
return self._get_extension(b'keyUsage')
|
||||
|
||||
def _get_extended_key_usage(self):
|
||||
return self._get_extension(b'extendedKeyUsage')
|
||||
|
||||
def _get_basic_constraints(self):
|
||||
return self._get_extension(b'basicConstraints')
|
||||
|
||||
def _get_ocsp_must_staple(self):
|
||||
extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())]
|
||||
oms_ext = [
|
||||
ext for ext in extensions
|
||||
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
|
||||
]
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
|
||||
# Older versions of libssl don't know about OCSP Must Staple
|
||||
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
|
||||
if oms_ext:
|
||||
return True, bool(oms_ext[0].get_critical())
|
||||
else:
|
||||
return None, False
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
return result, bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def get_not_before(self):
|
||||
time_string = to_native(self.cert.get_notBefore())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def get_not_after(self):
|
||||
time_string = to_native(self.cert.get_notAfter())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def _get_public_key_pem(self):
|
||||
def _get_issuer_uri(self):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_PEM,
|
||||
self.cert.get_pubkey(),
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
return crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
|
||||
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
|
||||
|
||||
def _get_public_key_object(self):
|
||||
return self.cert.get_pubkey()
|
||||
|
||||
def _get_subject_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None
|
||||
|
||||
def _get_authority_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None, None, None
|
||||
|
||||
def _get_serial_number(self):
|
||||
return self.cert.get_serial_number()
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return pyopenssl_get_extensions_from_cert(self.cert)
|
||||
|
||||
def _get_ocsp_uri(self):
|
||||
for i in range(self.cert.get_extension_count()):
|
||||
ext = self.cert.get_extension(i)
|
||||
if ext.get_short_name() == b'authorityInfoAccess':
|
||||
v = str(ext)
|
||||
m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE)
|
||||
if m:
|
||||
return m.group(1)
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
|
||||
for desc in ext.value:
|
||||
if desc.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS:
|
||||
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
|
||||
return desc.access_location.value
|
||||
except x509.ExtensionNotFound as dummy:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def get_certificate_info(module, backend, content, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = CertificateInfoRetrievalCryptography(module, content)
|
||||
elif backend == 'pyopenssl':
|
||||
info = CertificateInfoRetrievalPyOpenSSL(module, content)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
@@ -529,34 +392,17 @@ def select_backend(module, backend, content):
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
module.fail_json(msg=("Cannot detect any of the required Python libraries "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, CertificateInfoRetrievalPyOpenSSL(module, content)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -12,8 +13,6 @@ import os
|
||||
|
||||
from random import randrange
|
||||
|
||||
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 (
|
||||
@@ -41,11 +40,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
CertificateProvider,
|
||||
)
|
||||
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
@@ -175,7 +169,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
||||
return self.cert.public_bytes(Encoding.PEM)
|
||||
|
||||
def needs_regeneration(self):
|
||||
if super(OwnCACertificateBackendCryptography, self).needs_regeneration():
|
||||
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter):
|
||||
return True
|
||||
|
||||
self._ensure_existing_certificate_loaded()
|
||||
@@ -242,112 +236,6 @@ def generate_serial_number():
|
||||
return result
|
||||
|
||||
|
||||
class OwnCACertificateBackendPyOpenSSL(CertificateBackend):
|
||||
def __init__(self, module):
|
||||
super(OwnCACertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
self.notBefore = get_relative_time_option(self.module.params['ownca_not_before'], 'ownca_not_before', backend=self.backend)
|
||||
self.notAfter = get_relative_time_option(self.module.params['ownca_not_after'], 'ownca_not_after', backend=self.backend)
|
||||
self.digest = self.module.params['ownca_digest']
|
||||
self.version = self.module.params['ownca_version']
|
||||
self.serial_number = generate_serial_number()
|
||||
if self.module.params['ownca_create_subject_key_identifier'] != 'create_if_not_provided':
|
||||
self.module.fail_json(msg='ownca_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
|
||||
if self.module.params['ownca_create_authority_key_identifier']:
|
||||
self.module.warn('ownca_create_authority_key_identifier is ignored by the pyOpenSSL backend!')
|
||||
self.ca_cert_path = self.module.params['ownca_path']
|
||||
self.ca_cert_content = self.module.params['ownca_content']
|
||||
if self.ca_cert_content is not None:
|
||||
self.ca_cert_content = self.ca_cert_content.encode('utf-8')
|
||||
self.ca_privatekey_path = self.module.params['ownca_privatekey_path']
|
||||
self.ca_privatekey_content = self.module.params['ownca_privatekey_content']
|
||||
if self.ca_privatekey_content is not None:
|
||||
self.ca_privatekey_content = self.ca_privatekey_content.encode('utf-8')
|
||||
self.ca_privatekey_passphrase = self.module.params['ownca_privatekey_passphrase']
|
||||
|
||||
if self.csr_content is None and not os.path.exists(self.csr_path):
|
||||
raise CertificateError(
|
||||
'The certificate signing request file {0} does not exist'.format(self.csr_path)
|
||||
)
|
||||
if self.ca_cert_content is None and not os.path.exists(self.ca_cert_path):
|
||||
raise CertificateError(
|
||||
'The CA certificate file {0} does not exist'.format(self.ca_cert_path)
|
||||
)
|
||||
if self.ca_privatekey_content is None and not os.path.exists(self.ca_privatekey_path):
|
||||
raise CertificateError(
|
||||
'The CA private key file {0} does not exist'.format(self.ca_privatekey_path)
|
||||
)
|
||||
|
||||
self._ensure_csr_loaded()
|
||||
self.ca_cert = load_certificate(
|
||||
path=self.ca_cert_path,
|
||||
content=self.ca_cert_content,
|
||||
)
|
||||
try:
|
||||
self.ca_privatekey = load_privatekey(
|
||||
path=self.ca_privatekey_path,
|
||||
content=self.ca_privatekey_content,
|
||||
passphrase=self.ca_privatekey_passphrase
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
self.module.fail_json(msg=str(exc))
|
||||
|
||||
def generate_certificate(self):
|
||||
"""(Re-)Generate certificate."""
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(self.serial_number)
|
||||
cert.set_notBefore(to_bytes(self.notBefore))
|
||||
cert.set_notAfter(to_bytes(self.notAfter))
|
||||
cert.set_subject(self.csr.get_subject())
|
||||
cert.set_issuer(self.ca_cert.get_subject())
|
||||
cert.set_version(self.version - 1)
|
||||
cert.set_pubkey(self.csr.get_pubkey())
|
||||
cert.add_extensions(self.csr.get_extensions())
|
||||
|
||||
cert.sign(self.ca_privatekey, self.digest)
|
||||
self.cert = cert
|
||||
|
||||
def get_certificate_data(self):
|
||||
"""Return bytes for 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):
|
||||
result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate)
|
||||
result.update({
|
||||
'ca_cert': self.ca_cert_path,
|
||||
'ca_privatekey': self.ca_privatekey_path,
|
||||
})
|
||||
|
||||
if self.module.check_mode:
|
||||
result.update({
|
||||
'notBefore': self.notBefore,
|
||||
'notAfter': self.notAfter,
|
||||
'serial_number': self.serial_number,
|
||||
})
|
||||
else:
|
||||
if self.cert is None:
|
||||
self.cert = self.existing_certificate
|
||||
result.update({
|
||||
'notBefore': self.cert.get_notBefore(),
|
||||
'notAfter': self.cert.get_notAfter(),
|
||||
'serial_number': self.cert.get_serial_number(),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class OwnCACertificateProvider(CertificateProvider):
|
||||
def validate_module_args(self, module):
|
||||
if module.params['ownca_path'] is None and module.params['ownca_content'] is None:
|
||||
@@ -361,8 +249,6 @@ class OwnCACertificateProvider(CertificateProvider):
|
||||
def create_backend(self, module, backend):
|
||||
if backend == 'cryptography':
|
||||
return OwnCACertificateBackendCryptography(module)
|
||||
if backend == 'pyopenssl':
|
||||
return OwnCACertificateBackendPyOpenSSL(module)
|
||||
|
||||
|
||||
def add_ownca_provider_to_argument_spec(argument_spec):
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -12,8 +13,6 @@ import os
|
||||
|
||||
from random import randrange
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
get_relative_time_option,
|
||||
select_message_digest,
|
||||
@@ -31,11 +30,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
CertificateProvider,
|
||||
)
|
||||
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
@@ -136,7 +130,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
||||
return self.cert.public_bytes(Encoding.PEM)
|
||||
|
||||
def needs_regeneration(self):
|
||||
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration():
|
||||
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter):
|
||||
return True
|
||||
|
||||
self._ensure_existing_certificate_loaded()
|
||||
@@ -176,76 +170,6 @@ def generate_serial_number():
|
||||
return result
|
||||
|
||||
|
||||
class SelfSignedCertificateBackendPyOpenSSL(CertificateBackend):
|
||||
def __init__(self, module):
|
||||
super(SelfSignedCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
if module.params['selfsigned_create_subject_key_identifier'] != 'create_if_not_provided':
|
||||
module.fail_json(msg='selfsigned_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
|
||||
self.notBefore = get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before', backend=self.backend)
|
||||
self.notAfter = get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after', backend=self.backend)
|
||||
self.digest = module.params['selfsigned_digest']
|
||||
self.version = module.params['selfsigned_version']
|
||||
self.serial_number = generate_serial_number()
|
||||
|
||||
if self.csr_path is not None and not os.path.exists(self.csr_path):
|
||||
raise CertificateError(
|
||||
'The certificate signing request file {0} does not exist'.format(self.csr_path)
|
||||
)
|
||||
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
|
||||
raise CertificateError(
|
||||
'The private key file {0} does not exist'.format(self.privatekey_path)
|
||||
)
|
||||
|
||||
self._ensure_private_key_loaded()
|
||||
|
||||
self._ensure_csr_loaded()
|
||||
if self.csr is None:
|
||||
# Create empty CSR on the fly
|
||||
self.csr = crypto.X509Req()
|
||||
self.csr.set_pubkey(self.privatekey)
|
||||
self.csr.sign(self.privatekey, self.digest)
|
||||
|
||||
def generate_certificate(self):
|
||||
"""(Re-)Generate certificate."""
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(self.serial_number)
|
||||
cert.set_notBefore(to_bytes(self.notBefore))
|
||||
cert.set_notAfter(to_bytes(self.notAfter))
|
||||
cert.set_subject(self.csr.get_subject())
|
||||
cert.set_issuer(self.csr.get_subject())
|
||||
cert.set_version(self.version - 1)
|
||||
cert.set_pubkey(self.csr.get_pubkey())
|
||||
cert.add_extensions(self.csr.get_extensions())
|
||||
|
||||
cert.sign(self.privatekey, self.digest)
|
||||
self.cert = cert
|
||||
|
||||
def get_certificate_data(self):
|
||||
"""Return bytes for self.cert."""
|
||||
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
|
||||
|
||||
def dump(self, include_certificate):
|
||||
result = super(SelfSignedCertificateBackendPyOpenSSL, self).dump(include_certificate)
|
||||
|
||||
if self.module.check_mode:
|
||||
result.update({
|
||||
'notBefore': self.notBefore,
|
||||
'notAfter': self.notAfter,
|
||||
'serial_number': self.serial_number,
|
||||
})
|
||||
else:
|
||||
if self.cert is None:
|
||||
self.cert = self.existing_certificate
|
||||
result.update({
|
||||
'notBefore': self.cert.get_notBefore(),
|
||||
'notAfter': self.cert.get_notAfter(),
|
||||
'serial_number': self.cert.get_serial_number(),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class SelfSignedCertificateProvider(CertificateProvider):
|
||||
def validate_module_args(self, module):
|
||||
if module.params['privatekey_path'] is None and module.params['privatekey_content'] is None:
|
||||
@@ -257,8 +181,6 @@ class SelfSignedCertificateProvider(CertificateProvider):
|
||||
def create_backend(self, module, backend):
|
||||
if backend == 'cryptography':
|
||||
return SelfSignedCertificateBackendCryptography(module)
|
||||
if backend == 'pyopenssl':
|
||||
return SelfSignedCertificateBackendPyOpenSSL(module)
|
||||
|
||||
|
||||
def add_selfsigned_provider_to_argument_spec(argument_spec):
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -51,6 +52,7 @@ class CRLInfoRetrieval(object):
|
||||
self.module = module
|
||||
self.content = content
|
||||
self.list_revoked_certificates = list_revoked_certificates
|
||||
self.name_encoding = module.params.get('name_encoding', 'ignore')
|
||||
|
||||
def get_info(self):
|
||||
self.crl_pem = identify_pem_format(self.content)
|
||||
@@ -86,7 +88,7 @@ class CRLInfoRetrieval(object):
|
||||
result['revoked_certificates'] = []
|
||||
for cert in self.crl:
|
||||
entry = cryptography_decode_revoked_certificate(cert)
|
||||
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
|
||||
result['revoked_certificates'].append(cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -14,7 +15,7 @@ import traceback
|
||||
|
||||
from ansible.module_utils import six
|
||||
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_native, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||
|
||||
@@ -27,6 +28,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
||||
load_privatekey,
|
||||
load_certificate_request,
|
||||
parse_name_field,
|
||||
parse_ordered_name_field,
|
||||
select_message_digest,
|
||||
)
|
||||
|
||||
@@ -43,11 +45,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
REVOCATION_REASON_MAP,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_normalize_name_attribute,
|
||||
pyopenssl_parse_name_constraints,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.csr_info import (
|
||||
get_csr_info,
|
||||
)
|
||||
@@ -55,28 +52,8 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
||||
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# OpenSSL 1.1.0 or newer
|
||||
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
|
||||
else:
|
||||
# OpenSSL 1.0.x or older
|
||||
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
@@ -144,6 +121,7 @@ class CertificateSigningRequestBackend(object):
|
||||
if self.create_subject_key_identifier and self.subject_key_identifier is not None:
|
||||
module.fail_json(msg='subject_key_identifier cannot be specified if create_subject_key_identifier is true')
|
||||
|
||||
self.ordered_subject = False
|
||||
self.subject = [
|
||||
('C', module.params['country_name']),
|
||||
('ST', module.params['state_or_province_name']),
|
||||
@@ -153,11 +131,19 @@ class CertificateSigningRequestBackend(object):
|
||||
('CN', module.params['common_name']),
|
||||
('emailAddress', module.params['email_address']),
|
||||
]
|
||||
|
||||
if module.params['subject']:
|
||||
self.subject = self.subject + parse_name_field(module.params['subject'])
|
||||
self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]]
|
||||
|
||||
try:
|
||||
if module.params['subject']:
|
||||
self.subject = self.subject + parse_name_field(module.params['subject'], 'subject')
|
||||
if module.params['subject_ordered']:
|
||||
if self.subject:
|
||||
raise CertificateSigningRequestError('subject_ordered cannot be combined with any other subject field')
|
||||
self.subject = parse_ordered_name_field(module.params['subject_ordered'], 'subject_ordered')
|
||||
self.ordered_subject = True
|
||||
except ValueError as exc:
|
||||
raise CertificateSigningRequestError(to_native(exc))
|
||||
|
||||
self.using_common_name_for_san = False
|
||||
if not self.subjectAltName and module.params['use_common_name_for_san']:
|
||||
for sub in self.subject:
|
||||
@@ -273,174 +259,6 @@ class CertificateSigningRequestBackend(object):
|
||||
return result
|
||||
|
||||
|
||||
# Implementation with using pyOpenSSL
|
||||
class CertificateSigningRequestPyOpenSSLBackend(CertificateSigningRequestBackend):
|
||||
def __init__(self, module):
|
||||
for o in ('create_subject_key_identifier', ):
|
||||
if module.params[o]:
|
||||
module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
|
||||
for o in ('subject_key_identifier', 'authority_key_identifier', 'authority_cert_issuer', 'authority_cert_serial_number', 'crl_distribution_points'):
|
||||
if module.params[o] is not None:
|
||||
module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
|
||||
super(CertificateSigningRequestPyOpenSSLBackend, self).__init__(module, 'pyopenssl')
|
||||
|
||||
def generate_csr(self):
|
||||
"""(Re-)Generate CSR."""
|
||||
self._ensure_private_key_loaded()
|
||||
|
||||
req = crypto.X509Req()
|
||||
req.set_version(self.version - 1)
|
||||
subject = req.get_subject()
|
||||
for entry in self.subject:
|
||||
if entry[1] is not None:
|
||||
# Workaround for https://github.com/pyca/pyopenssl/issues/165
|
||||
nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(entry[0]))
|
||||
if nid == 0:
|
||||
raise CertificateSigningRequestError('Unknown subject field identifier "{0}"'.format(entry[0]))
|
||||
res = OpenSSL._util.lib.X509_NAME_add_entry_by_NID(subject._name, nid, OpenSSL._util.lib.MBSTRING_UTF8, to_bytes(entry[1]), -1, -1, 0)
|
||||
if res == 0:
|
||||
raise CertificateSigningRequestError('Invalid value for subject field identifier "{0}": {1}'.format(entry[0], entry[1]))
|
||||
|
||||
extensions = []
|
||||
if self.subjectAltName:
|
||||
altnames = ', '.join(self.subjectAltName)
|
||||
try:
|
||||
extensions.append(crypto.X509Extension(b"subjectAltName", self.subjectAltName_critical, altnames.encode('ascii')))
|
||||
except OpenSSL.crypto.Error as e:
|
||||
raise CertificateSigningRequestError(
|
||||
'Error while parsing Subject Alternative Names {0} (check for missing type prefix, such as "DNS:"!): {1}'.format(
|
||||
', '.join(["{0}".format(san) for san in self.subjectAltName]), str(e)
|
||||
)
|
||||
)
|
||||
|
||||
if self.keyUsage:
|
||||
usages = ', '.join(self.keyUsage)
|
||||
extensions.append(crypto.X509Extension(b"keyUsage", self.keyUsage_critical, usages.encode('ascii')))
|
||||
|
||||
if self.extendedKeyUsage:
|
||||
usages = ', '.join(self.extendedKeyUsage)
|
||||
extensions.append(crypto.X509Extension(b"extendedKeyUsage", self.extendedKeyUsage_critical, usages.encode('ascii')))
|
||||
|
||||
if self.basicConstraints:
|
||||
usages = ', '.join(self.basicConstraints)
|
||||
extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii')))
|
||||
|
||||
if self.name_constraints_permitted or self.name_constraints_excluded:
|
||||
usages = ', '.join(
|
||||
['permitted;{0}'.format(name) for name in self.name_constraints_permitted] +
|
||||
['excluded;{0}'.format(name) for name in self.name_constraints_excluded]
|
||||
)
|
||||
extensions.append(crypto.X509Extension(b"nameConstraints", self.name_constraints_critical, usages.encode('ascii')))
|
||||
|
||||
if self.ocspMustStaple:
|
||||
extensions.append(crypto.X509Extension(OPENSSL_MUST_STAPLE_NAME, self.ocspMustStaple_critical, OPENSSL_MUST_STAPLE_VALUE))
|
||||
|
||||
if extensions:
|
||||
req.add_extensions(extensions)
|
||||
|
||||
req.set_pubkey(self.privatekey)
|
||||
req.sign(self.privatekey, self.digest)
|
||||
self.csr = req
|
||||
|
||||
def get_csr_data(self):
|
||||
"""Return bytes for self.csr."""
|
||||
return crypto.dump_certificate_request(crypto.FILETYPE_PEM, self.csr)
|
||||
|
||||
def _check_csr(self):
|
||||
def _check_subject(csr):
|
||||
subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in self.subject]
|
||||
current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in csr.get_subject().get_components()]
|
||||
if not set(subject) == set(current_subject):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _check_subjectAltName(extensions):
|
||||
altnames_ext = next((ext for ext in extensions if ext.get_short_name() == b'subjectAltName'), '')
|
||||
altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
to_text(altnames_ext, errors='surrogate_or_strict').split(',') if altname.strip()]
|
||||
if self.subjectAltName:
|
||||
if (set(altnames) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.subjectAltName]) or
|
||||
altnames_ext.get_critical() != self.subjectAltName_critical):
|
||||
return False
|
||||
else:
|
||||
if altnames:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _check_keyUsage_(extensions, extName, expected, critical):
|
||||
usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
|
||||
if (not usages_ext and expected) or (usages_ext and not expected):
|
||||
return False
|
||||
elif not usages_ext and not expected:
|
||||
return True
|
||||
else:
|
||||
current = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip())) for usage in str(usages_ext[0]).split(',')]
|
||||
expected = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected]
|
||||
return set(current) == set(expected) and usages_ext[0].get_critical() == critical
|
||||
|
||||
def _check_keyUsage(extensions):
|
||||
usages_ext = [ext for ext in extensions if ext.get_short_name() == b'keyUsage']
|
||||
if (not usages_ext and self.keyUsage) or (usages_ext and not self.keyUsage):
|
||||
return False
|
||||
elif not usages_ext and not self.keyUsage:
|
||||
return True
|
||||
else:
|
||||
# OpenSSL._util.lib.OBJ_txt2nid() always returns 0 for all keyUsage values
|
||||
# (since keyUsage has a fixed bitfield for these values and is not extensible).
|
||||
# Therefore, we create an extension for the wanted values, and compare the
|
||||
# data of the extensions (which is the serialized bitfield).
|
||||
expected_ext = crypto.X509Extension(b"keyUsage", False, ', '.join(self.keyUsage).encode('ascii'))
|
||||
return usages_ext[0].get_data() == expected_ext.get_data() and usages_ext[0].get_critical() == self.keyUsage_critical
|
||||
|
||||
def _check_extenededKeyUsage(extensions):
|
||||
return _check_keyUsage_(extensions, b'extendedKeyUsage', self.extendedKeyUsage, self.extendedKeyUsage_critical)
|
||||
|
||||
def _check_basicConstraints(extensions):
|
||||
return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical)
|
||||
|
||||
def _check_nameConstraints(extensions):
|
||||
nc_ext = next((ext for ext in extensions if ext.get_short_name() == b'nameConstraints'), '')
|
||||
permitted, excluded = pyopenssl_parse_name_constraints(nc_ext)
|
||||
if self.name_constraints_permitted or self.name_constraints_excluded:
|
||||
if set(permitted) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_permitted]):
|
||||
return False
|
||||
if set(excluded) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_excluded]):
|
||||
return False
|
||||
if nc_ext.get_critical() != self.name_constraints_critical:
|
||||
return False
|
||||
else:
|
||||
if permitted or excluded:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _check_ocspMustStaple(extensions):
|
||||
oms_ext = [ext for ext in extensions if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE]
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
|
||||
# Older versions of libssl don't know about OCSP Must Staple
|
||||
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
|
||||
if self.ocspMustStaple:
|
||||
return len(oms_ext) > 0 and oms_ext[0].get_critical() == self.ocspMustStaple_critical
|
||||
else:
|
||||
return len(oms_ext) == 0
|
||||
|
||||
def _check_extensions(csr):
|
||||
extensions = csr.get_extensions()
|
||||
return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
|
||||
_check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
|
||||
_check_ocspMustStaple(extensions) and _check_nameConstraints(extensions))
|
||||
|
||||
def _check_signature(csr):
|
||||
try:
|
||||
return csr.verify(self.privatekey)
|
||||
except crypto.Error:
|
||||
return False
|
||||
|
||||
return _check_subject(self.existing_csr) and _check_extensions(self.existing_csr) and _check_signature(self.existing_csr)
|
||||
|
||||
|
||||
def parse_crl_distribution_points(module, crl_distribution_points):
|
||||
result = []
|
||||
for index, parse_crl_distribution_point in enumerate(crl_distribution_points):
|
||||
@@ -600,7 +418,10 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
def _check_subject(csr):
|
||||
subject = [(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject]
|
||||
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
|
||||
return set(subject) == set(current_subject)
|
||||
if self.ordered_subject:
|
||||
return subject == current_subject
|
||||
else:
|
||||
return set(subject) == set(current_subject)
|
||||
|
||||
def _find_extension(extensions, exttype):
|
||||
return next(
|
||||
@@ -760,42 +581,20 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
|
||||
|
||||
def select_backend(module, backend):
|
||||
if module.params['version'] != 1:
|
||||
module.deprecate('The version option will only support allowed values from community.crypto 2.0.0 on. '
|
||||
'Currently, only the value 1 is allowed by RFC 2986',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
module.fail_json(msg=("Cannot detect any of the required Python libraries "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, CertificateSigningRequestPyOpenSSLBackend(module)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
@@ -811,8 +610,9 @@ def get_csr_argument_spec():
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
version=dict(type='int', default=1),
|
||||
version=dict(type='int', default=1, choices=[1]),
|
||||
subject=dict(type='dict'),
|
||||
subject_ordered=dict(type='list', elements='dict'),
|
||||
country_name=dict(type='str', aliases=['C', 'countryName']),
|
||||
state_or_province_name=dict(type='str', aliases=['ST', 'stateOrProvinceName']),
|
||||
locality_name=dict(type='str', aliases=['L', 'localityName']),
|
||||
@@ -860,13 +660,14 @@ def get_csr_argument_spec():
|
||||
mutually_exclusive=[('full_name', 'relative_name')],
|
||||
required_one_of=[('full_name', 'relative_name', 'crl_issuer')],
|
||||
),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_together=[
|
||||
['authority_cert_issuer', 'authority_cert_serial_number'],
|
||||
],
|
||||
mutually_exclusive=[
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
['subject', 'subject_ordered'],
|
||||
],
|
||||
required_one_of=[
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -15,13 +16,12 @@ import traceback
|
||||
|
||||
from ansible.module_utils import six
|
||||
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
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
load_certificate_request,
|
||||
get_fingerprint_of_bytes,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
@@ -30,38 +30,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
cryptography_oid_to_name,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_get_extensions_from_csr,
|
||||
pyopenssl_normalize_name,
|
||||
pyopenssl_normalize_name_attribute,
|
||||
pyopenssl_parse_name_constraints,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
get_publickey_info,
|
||||
)
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# OpenSSL 1.1.0 or newer
|
||||
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
|
||||
else:
|
||||
# OpenSSL 1.0.x or older
|
||||
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -160,7 +133,7 @@ class CSRInfoRetrieval(object):
|
||||
result['name_constraints_critical'],
|
||||
) = self._get_name_constraints()
|
||||
|
||||
result['public_key'] = self._get_public_key_pem()
|
||||
result['public_key'] = to_native(self._get_public_key_pem())
|
||||
|
||||
public_key_info = get_publickey_info(
|
||||
self.module,
|
||||
@@ -173,20 +146,19 @@ class CSRInfoRetrieval(object):
|
||||
'public_key_fingerprints': public_key_info['fingerprints'],
|
||||
})
|
||||
|
||||
if self.backend != 'pyopenssl':
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
|
||||
result['extensions_by_oid'] = self._get_all_extensions()
|
||||
|
||||
@@ -203,6 +175,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
"""Validate the supplied CSR, using the cryptography backend"""
|
||||
def __init__(self, module, content, validate_signature):
|
||||
super(CSRInfoRetrievalCryptography, self).__init__(module, 'cryptography', content, validate_signature)
|
||||
self.name_encoding = module.params.get('name_encoding', 'ignore')
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
result = []
|
||||
@@ -285,7 +258,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
def _get_subject_alt_name(self):
|
||||
try:
|
||||
san_ext = self.csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
result = [cryptography_decode_name(san) for san in san_ext.value]
|
||||
result = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in san_ext.value]
|
||||
return result, san_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
@@ -293,8 +266,8 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
def _get_name_constraints(self):
|
||||
try:
|
||||
nc_ext = self.csr.extensions.get_extension_for_class(x509.NameConstraints)
|
||||
permitted = [cryptography_decode_name(san) for san in nc_ext.value.permitted_subtrees or []]
|
||||
excluded = [cryptography_decode_name(san) for san in nc_ext.value.excluded_subtrees or []]
|
||||
permitted = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in nc_ext.value.permitted_subtrees or []]
|
||||
excluded = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in nc_ext.value.excluded_subtrees or []]
|
||||
return permitted, excluded, nc_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, None, False
|
||||
@@ -320,7 +293,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
ext = self.csr.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
|
||||
issuer = None
|
||||
if ext.value.authority_cert_issuer is not None:
|
||||
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
|
||||
issuer = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in ext.value.authority_cert_issuer]
|
||||
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, None, None
|
||||
@@ -332,112 +305,9 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
return self.csr.is_signature_valid
|
||||
|
||||
|
||||
class CSRInfoRetrievalPyOpenSSL(CSRInfoRetrieval):
|
||||
"""validate the supplied CSR."""
|
||||
|
||||
def __init__(self, module, content, validate_signature):
|
||||
super(CSRInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content, validate_signature)
|
||||
|
||||
def __get_name(self, name):
|
||||
result = []
|
||||
for sub in name.get_components():
|
||||
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
|
||||
return result
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
return self.__get_name(self.csr.get_subject())
|
||||
|
||||
def _get_extension(self, short_name):
|
||||
for extension in self.csr.get_extensions():
|
||||
if extension.get_short_name() == short_name:
|
||||
result = [
|
||||
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
|
||||
]
|
||||
return sorted(result), bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_key_usage(self):
|
||||
return self._get_extension(b'keyUsage')
|
||||
|
||||
def _get_extended_key_usage(self):
|
||||
return self._get_extension(b'extendedKeyUsage')
|
||||
|
||||
def _get_basic_constraints(self):
|
||||
return self._get_extension(b'basicConstraints')
|
||||
|
||||
def _get_ocsp_must_staple(self):
|
||||
extensions = self.csr.get_extensions()
|
||||
oms_ext = [
|
||||
ext for ext in extensions
|
||||
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
|
||||
]
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
|
||||
# Older versions of libssl don't know about OCSP Must Staple
|
||||
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
|
||||
if oms_ext:
|
||||
return True, bool(oms_ext[0].get_critical())
|
||||
else:
|
||||
return None, False
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
for extension in self.csr.get_extensions():
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
return result, bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_name_constraints(self):
|
||||
for extension in self.csr.get_extensions():
|
||||
if extension.get_short_name() == b'nameConstraints':
|
||||
permitted, excluded = pyopenssl_parse_name_constraints(extension)
|
||||
return permitted, excluded, bool(extension.get_critical())
|
||||
return None, None, False
|
||||
|
||||
def _get_public_key_pem(self):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_PEM,
|
||||
self.csr.get_pubkey(),
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
bio = crypto._new_mem_buf()
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.csr.get_pubkey()._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
return crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
|
||||
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
|
||||
|
||||
def _get_public_key_object(self):
|
||||
return self.csr.get_pubkey()
|
||||
|
||||
def _get_subject_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None
|
||||
|
||||
def _get_authority_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None, None, None
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return pyopenssl_get_extensions_from_csr(self.csr)
|
||||
|
||||
def _is_signature_valid(self):
|
||||
try:
|
||||
return bool(self.csr.verify(self.csr.get_pubkey()))
|
||||
except crypto.Error:
|
||||
# OpenSSL error means that key is not consistent
|
||||
return False
|
||||
|
||||
|
||||
def get_csr_info(module, backend, content, validate_signature=True, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = CSRInfoRetrievalCryptography(module, content, validate_signature=validate_signature)
|
||||
elif backend == 'pyopenssl':
|
||||
info = CSRInfoRetrievalPyOpenSSL(module, content, validate_signature=validate_signature)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
@@ -445,34 +315,17 @@ def select_backend(module, backend, content, validate_signature=True):
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
module.fail_json(msg=("Cannot detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, CSRInfoRetrievalPyOpenSSL(module, content, validate_signature=validate_signature)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -25,11 +26,9 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
load_privatekey,
|
||||
get_fingerprint_of_privatekey,
|
||||
)
|
||||
|
||||
@@ -46,20 +45,8 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
||||
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.6'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
@@ -188,7 +175,7 @@ class PrivateKeyBackend:
|
||||
return True
|
||||
self.module.fail_json(msg='Unable to read the key. The key is protected with a another passphrase / no passphrase or broken.'
|
||||
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||
' set to `full_idempotence` or `always`, or with `force=yes`.')
|
||||
' set to `full_idempotence` or `always`, or with `force=true`.')
|
||||
self._ensure_existing_private_key_loaded()
|
||||
if self.regenerate != 'never':
|
||||
if not self._check_size_and_type():
|
||||
@@ -196,7 +183,7 @@ class PrivateKeyBackend:
|
||||
return True
|
||||
self.module.fail_json(msg='Key has wrong type and/or size.'
|
||||
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.')
|
||||
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`.')
|
||||
# During generation step, regenerate if format does not match and format_mismatch == 'regenerate'
|
||||
if self.format_mismatch == 'regenerate' and self.regenerate != 'never':
|
||||
if not self._check_format():
|
||||
@@ -204,7 +191,7 @@ class PrivateKeyBackend:
|
||||
return True
|
||||
self.module.fail_json(msg='Key has wrong format.'
|
||||
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.'
|
||||
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`.'
|
||||
' To convert the key, set `format_mismatch` to `convert`.')
|
||||
return False
|
||||
|
||||
@@ -263,61 +250,6 @@ class PrivateKeyBackend:
|
||||
return result
|
||||
|
||||
|
||||
# Implementation with using pyOpenSSL
|
||||
class PrivateKeyPyOpenSSLBackend(PrivateKeyBackend):
|
||||
|
||||
def __init__(self, module):
|
||||
super(PrivateKeyPyOpenSSLBackend, self).__init__(module=module, backend='pyopenssl')
|
||||
|
||||
if self.type == 'RSA':
|
||||
self.openssl_type = crypto.TYPE_RSA
|
||||
elif self.type == 'DSA':
|
||||
self.openssl_type = crypto.TYPE_DSA
|
||||
else:
|
||||
self.module.fail_json(msg="PyOpenSSL backend only supports RSA and DSA keys.")
|
||||
|
||||
if self.format != 'auto_ignore':
|
||||
self.module.fail_json(msg="PyOpenSSL backend only supports auto_ignore format.")
|
||||
|
||||
def generate_private_key(self):
|
||||
"""(Re-)Generate private key."""
|
||||
self.private_key = crypto.PKey()
|
||||
try:
|
||||
self.private_key.generate_key(self.openssl_type, self.size)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise PrivateKeyError(exc)
|
||||
|
||||
def _ensure_existing_private_key_loaded(self):
|
||||
if self.existing_private_key is None and self.has_existing():
|
||||
try:
|
||||
self.existing_private_key = load_privatekey(
|
||||
None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
raise PrivateKeyError(exc)
|
||||
|
||||
def get_private_key_data(self):
|
||||
"""Return bytes for self.private_key"""
|
||||
if self.cipher and self.passphrase:
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key,
|
||||
self.cipher, to_bytes(self.passphrase))
|
||||
else:
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key)
|
||||
|
||||
def _check_passphrase(self):
|
||||
try:
|
||||
load_privatekey(None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend)
|
||||
return True
|
||||
except Exception as dummy:
|
||||
return False
|
||||
|
||||
def _check_size_and_type(self):
|
||||
return self.size == self.existing_private_key.bits() and self.openssl_type == self.existing_private_key.type()
|
||||
|
||||
def _check_format(self):
|
||||
# Not supported by this backend
|
||||
return True
|
||||
|
||||
|
||||
# Implementation with using cryptography
|
||||
class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
|
||||
@@ -550,36 +482,16 @@ def select_backend(module, backend):
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# Decision
|
||||
if module.params['cipher'] and module.params['passphrase'] and module.params['cipher'] != 'auto':
|
||||
# First try pyOpenSSL, then cryptography
|
||||
if can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
elif can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
else:
|
||||
# First try cryptography, then pyOpenSSL
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, PrivateKeyPyOpenSSLBackend(module)
|
||||
elif backend == 'cryptography':
|
||||
module.fail_json(msg=("Cannot detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
@@ -605,7 +517,7 @@ def get_privatekey_argument_spec():
|
||||
cipher=dict(type='str'),
|
||||
format=dict(type='str', default='auto_ignore', choices=['pkcs1', 'pkcs8', 'raw', 'auto', 'auto_ignore']),
|
||||
format_mismatch=dict(type='str', default='regenerate', choices=['regenerate', 'convert']),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
regenerate=dict(
|
||||
type='str',
|
||||
default='full_idempotence',
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import abc
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils import six
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
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.io import (
|
||||
load_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
CRYPTOGRAPHY_HAS_X25519,
|
||||
CRYPTOGRAPHY_HAS_X448,
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_compare_private_keys,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||
identify_private_key_format,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
import cryptography.exceptions
|
||||
import cryptography.hazmat.backends
|
||||
import cryptography.hazmat.primitives.serialization
|
||||
import cryptography.hazmat.primitives.asymmetric.rsa
|
||||
import cryptography.hazmat.primitives.asymmetric.dsa
|
||||
import cryptography.hazmat.primitives.asymmetric.ec
|
||||
import cryptography.hazmat.primitives.asymmetric.utils
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
CRYPTOGRAPHY_FOUND = False
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
|
||||
class PrivateKeyError(OpenSSLObjectError):
|
||||
pass
|
||||
|
||||
|
||||
# From the object called `module`, only the following properties are used:
|
||||
#
|
||||
# - module.params[]
|
||||
# - module.warn(msg: str)
|
||||
# - module.fail_json(msg: str, **kwargs)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PrivateKeyConvertBackend:
|
||||
def __init__(self, module, backend):
|
||||
self.module = module
|
||||
self.src_path = module.params['src_path']
|
||||
self.src_content = module.params['src_content']
|
||||
self.src_passphrase = module.params['src_passphrase']
|
||||
self.format = module.params['format']
|
||||
self.dest_passphrase = module.params['dest_passphrase']
|
||||
self.backend = backend
|
||||
|
||||
self.src_private_key = None
|
||||
if self.src_path is not None:
|
||||
self.src_private_key_bytes = load_file(self.src_path, module)
|
||||
else:
|
||||
self.src_private_key_bytes = self.src_content.encode('utf-8')
|
||||
|
||||
self.dest_private_key = None
|
||||
self.dest_private_key_bytes = None
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_private_key_data(self):
|
||||
"""Return bytes for self.src_private_key in output format."""
|
||||
pass
|
||||
|
||||
def set_existing_destination(self, privatekey_bytes):
|
||||
"""Set existing private key bytes. None indicates that the key does not exist."""
|
||||
self.dest_private_key_bytes = privatekey_bytes
|
||||
|
||||
def has_existing_destination(self):
|
||||
"""Query whether an existing private key is/has been there."""
|
||||
return self.dest_private_key_bytes is not None
|
||||
|
||||
@abc.abstractmethod
|
||||
def _load_private_key(self, data, passphrase, current_hint=None):
|
||||
"""Check whether data cna be loaded as a private key with the provided passphrase. Return tuple (type, private_key)."""
|
||||
pass
|
||||
|
||||
def needs_conversion(self):
|
||||
"""Check whether a conversion is necessary. Must only be called if needs_regeneration() returned False."""
|
||||
dummy, self.src_private_key = self._load_private_key(self.src_private_key_bytes, self.src_passphrase)
|
||||
|
||||
if not self.has_existing_destination():
|
||||
return True
|
||||
|
||||
try:
|
||||
format, self.dest_private_key = self._load_private_key(self.dest_private_key_bytes, self.dest_passphrase, current_hint=self.src_private_key)
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
return format != self.format or not cryptography_compare_private_keys(self.dest_private_key, self.src_private_key)
|
||||
|
||||
def dump(self):
|
||||
"""Serialize the object into a dictionary."""
|
||||
return {}
|
||||
|
||||
|
||||
# Implementation with using cryptography
|
||||
class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
||||
def __init__(self, module):
|
||||
super(PrivateKeyConvertCryptographyBackend, self).__init__(module=module, backend='cryptography')
|
||||
|
||||
self.cryptography_backend = cryptography.hazmat.backends.default_backend()
|
||||
|
||||
def get_private_key_data(self):
|
||||
"""Return bytes for self.src_private_key in output format"""
|
||||
# Select export format and encoding
|
||||
try:
|
||||
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.PEM
|
||||
if self.format == 'pkcs1':
|
||||
# "TraditionalOpenSSL" format is PKCS1
|
||||
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
|
||||
elif self.format == 'pkcs8':
|
||||
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
|
||||
elif self.format == 'raw':
|
||||
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.Raw
|
||||
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.Raw
|
||||
except AttributeError:
|
||||
self.module.fail_json(msg='Cryptography backend does not support the selected output format "{0}"'.format(self.format))
|
||||
|
||||
# Select key encryption
|
||||
encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption()
|
||||
if self.dest_passphrase:
|
||||
encryption_algorithm = cryptography.hazmat.primitives.serialization.BestAvailableEncryption(to_bytes(self.dest_passphrase))
|
||||
|
||||
# Serialize key
|
||||
try:
|
||||
return self.src_private_key.private_bytes(
|
||||
encoding=export_encoding,
|
||||
format=export_format,
|
||||
encryption_algorithm=encryption_algorithm
|
||||
)
|
||||
except ValueError as dummy:
|
||||
self.module.fail_json(
|
||||
msg='Cryptography backend cannot serialize the private key in the required format "{0}"'.format(self.format)
|
||||
)
|
||||
except Exception as dummy:
|
||||
self.module.fail_json(
|
||||
msg='Error while serializing the private key in the required format "{0}"'.format(self.format),
|
||||
exception=traceback.format_exc()
|
||||
)
|
||||
|
||||
def _load_private_key(self, data, passphrase, current_hint=None):
|
||||
try:
|
||||
# Interpret bytes depending on format.
|
||||
format = identify_private_key_format(data)
|
||||
if format == 'raw':
|
||||
if passphrase is not None:
|
||||
raise PrivateKeyError('Cannot load raw key with passphrase')
|
||||
if len(data) == 56 and CRYPTOGRAPHY_HAS_X448:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(data)
|
||||
if len(data) == 57 and CRYPTOGRAPHY_HAS_ED448:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(data)
|
||||
if len(data) == 32:
|
||||
if CRYPTOGRAPHY_HAS_X25519 and not CRYPTOGRAPHY_HAS_ED25519:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
|
||||
if CRYPTOGRAPHY_HAS_ED25519 and not CRYPTOGRAPHY_HAS_X25519:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
|
||||
if CRYPTOGRAPHY_HAS_X25519 and CRYPTOGRAPHY_HAS_ED25519:
|
||||
if isinstance(current_hint, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey):
|
||||
try:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
|
||||
except Exception:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
|
||||
else:
|
||||
try:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
|
||||
except Exception:
|
||||
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
|
||||
raise PrivateKeyError('Cannot load raw key')
|
||||
else:
|
||||
return format, cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
data,
|
||||
None if passphrase is None else to_bytes(passphrase),
|
||||
backend=self.cryptography_backend
|
||||
)
|
||||
except Exception as e:
|
||||
raise PrivateKeyError(e)
|
||||
|
||||
|
||||
def select_backend(module):
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
return PrivateKeyConvertCryptographyBackend(module)
|
||||
|
||||
|
||||
def get_privatekey_argument_spec():
|
||||
return ArgumentSpec(
|
||||
argument_spec=dict(
|
||||
src_path=dict(type='path'),
|
||||
src_content=dict(type='str'),
|
||||
src_passphrase=dict(type='str', no_log=True),
|
||||
dest_passphrase=dict(type='str', no_log=True),
|
||||
format=dict(type='str', required=True, choices=['pkcs1', 'pkcs8', 'raw']),
|
||||
),
|
||||
mutually_exclusive=[
|
||||
['src_path', 'src_content'],
|
||||
],
|
||||
required_one_of=[
|
||||
['src_path', 'src_content'],
|
||||
],
|
||||
)
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -36,24 +37,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.math impor
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
_get_cryptography_public_key_info,
|
||||
_bigint_to_int,
|
||||
_get_pyopenssl_public_key_info,
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -69,20 +56,21 @@ else:
|
||||
SIGNATURE_TEST_DATA = b'1234'
|
||||
|
||||
|
||||
def _get_cryptography_private_key_info(key):
|
||||
def _get_cryptography_private_key_info(key, need_private_key_data=False):
|
||||
key_type, key_public_data = _get_cryptography_public_key_info(key.public_key())
|
||||
key_private_data = dict()
|
||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['p'] = private_numbers.p
|
||||
key_private_data['q'] = private_numbers.q
|
||||
key_private_data['exponent'] = private_numbers.d
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['x'] = private_numbers.x
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['multiplier'] = private_numbers.private_value
|
||||
if need_private_key_data:
|
||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['p'] = private_numbers.p
|
||||
key_private_data['q'] = private_numbers.q
|
||||
key_private_data['exponent'] = private_numbers.d
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['x'] = private_numbers.x
|
||||
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||
private_numbers = key.private_numbers()
|
||||
key_private_data['multiplier'] = private_numbers.private_value
|
||||
return key_type, key_public_data, key_private_data
|
||||
|
||||
|
||||
@@ -188,20 +176,21 @@ class PrivateKeyParseError(OpenSSLObjectError):
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PrivateKeyInfoRetrieval(object):
|
||||
def __init__(self, module, backend, content, passphrase=None, return_private_key_data=False):
|
||||
def __init__(self, module, backend, content, passphrase=None, return_private_key_data=False, check_consistency=False):
|
||||
# content must be a bytes string
|
||||
self.module = module
|
||||
self.backend = backend
|
||||
self.content = content
|
||||
self.passphrase = passphrase
|
||||
self.return_private_key_data = return_private_key_data
|
||||
self.check_consistency = check_consistency
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_public_key(self, binary):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_key_info(self):
|
||||
def _get_key_info(self, need_private_key_data=False):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -225,25 +214,27 @@ class PrivateKeyInfoRetrieval(object):
|
||||
except OpenSSLObjectError as exc:
|
||||
raise PrivateKeyParseError(to_native(exc), result)
|
||||
|
||||
result['public_key'] = self._get_public_key(binary=False)
|
||||
result['public_key'] = to_native(self._get_public_key(binary=False))
|
||||
pk = self._get_public_key(binary=True)
|
||||
result['public_key_fingerprints'] = get_fingerprint_of_bytes(
|
||||
pk, prefer_one=prefer_one_fingerprint) if pk is not None else dict()
|
||||
|
||||
key_type, key_public_data, key_private_data = self._get_key_info()
|
||||
key_type, key_public_data, key_private_data = self._get_key_info(
|
||||
need_private_key_data=self.return_private_key_data or self.check_consistency)
|
||||
result['type'] = key_type
|
||||
result['public_data'] = key_public_data
|
||||
if self.return_private_key_data:
|
||||
result['private_data'] = key_private_data
|
||||
|
||||
result['key_is_consistent'] = self._is_key_consistent(key_public_data, key_private_data)
|
||||
if result['key_is_consistent'] is False:
|
||||
# Only fail when it is False, to avoid to fail on None (which means "we don't know")
|
||||
msg = (
|
||||
"Private key is not consistent! (See "
|
||||
"https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html)"
|
||||
)
|
||||
raise PrivateKeyConsistencyError(msg, result)
|
||||
if self.check_consistency:
|
||||
result['key_is_consistent'] = self._is_key_consistent(key_public_data, key_private_data)
|
||||
if result['key_is_consistent'] is False:
|
||||
# Only fail when it is False, to avoid to fail on None (which means "we do not know")
|
||||
msg = (
|
||||
"Private key is not consistent! (See "
|
||||
"https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html)"
|
||||
)
|
||||
raise PrivateKeyConsistencyError(msg, result)
|
||||
return result
|
||||
|
||||
|
||||
@@ -258,177 +249,39 @@ class PrivateKeyInfoRetrievalCryptography(PrivateKeyInfoRetrieval):
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
|
||||
def _get_key_info(self):
|
||||
return _get_cryptography_private_key_info(self.key)
|
||||
def _get_key_info(self, need_private_key_data=False):
|
||||
return _get_cryptography_private_key_info(self.key, need_private_key_data=need_private_key_data)
|
||||
|
||||
def _is_key_consistent(self, key_public_data, key_private_data):
|
||||
return _is_cryptography_key_consistent(self.key, key_public_data, key_private_data)
|
||||
|
||||
|
||||
class PrivateKeyInfoRetrievalPyOpenSSL(PrivateKeyInfoRetrieval):
|
||||
"""validate the supplied private key."""
|
||||
|
||||
def __init__(self, module, content, **kwargs):
|
||||
super(PrivateKeyInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content, **kwargs)
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
|
||||
self.key
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
if binary:
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.key._pkey)
|
||||
else:
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.key._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
return crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
|
||||
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
|
||||
|
||||
def _get_key_info(self):
|
||||
key_type, key_public_data, try_fallback = _get_pyopenssl_public_key_info(self.key)
|
||||
key_private_data = dict()
|
||||
openssl_key_type = self.key.type()
|
||||
if crypto.TYPE_RSA == openssl_key_type:
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_RSA(self.key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.RSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get modulus and exponents
|
||||
n = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
e = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
d = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.RSA_get0_key(key, n, e, d)
|
||||
key_private_data['exponent'] = _bigint_to_int(d[0])
|
||||
# Get factors
|
||||
p = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
q = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.RSA_get0_factors(key, p, q)
|
||||
key_private_data['p'] = _bigint_to_int(p[0])
|
||||
key_private_data['q'] = _bigint_to_int(q[0])
|
||||
else:
|
||||
# Get private exponent
|
||||
key_private_data['exponent'] = _bigint_to_int(key.d)
|
||||
# Get factors
|
||||
key_private_data['p'] = _bigint_to_int(key.p)
|
||||
key_private_data['q'] = _bigint_to_int(key.q)
|
||||
except AttributeError:
|
||||
try_fallback = True
|
||||
elif crypto.TYPE_DSA == openssl_key_type:
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_DSA(self.key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.DSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get private key exponents
|
||||
y = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
x = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.DSA_get0_key(key, y, x)
|
||||
key_private_data['x'] = _bigint_to_int(x[0])
|
||||
else:
|
||||
# Get private key exponents
|
||||
key_private_data['x'] = _bigint_to_int(key.priv_key)
|
||||
except AttributeError:
|
||||
try_fallback = True
|
||||
else:
|
||||
# Return 'unknown'
|
||||
key_type = 'unknown ({0})'.format(self.key.type())
|
||||
# If needed and if possible, fall back to cryptography
|
||||
if try_fallback and PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND:
|
||||
return _get_cryptography_private_key_info(self.key.to_cryptography_key())
|
||||
return key_type, key_public_data, key_private_data
|
||||
|
||||
def _is_key_consistent(self, key_public_data, key_private_data):
|
||||
openssl_key_type = self.key.type()
|
||||
if crypto.TYPE_RSA == openssl_key_type:
|
||||
try:
|
||||
return self.key.check()
|
||||
except crypto.Error:
|
||||
# OpenSSL error means that key is not consistent
|
||||
return False
|
||||
if crypto.TYPE_DSA == openssl_key_type:
|
||||
result = _check_dsa_consistency(key_public_data, key_private_data)
|
||||
if result is not None:
|
||||
return result
|
||||
signature = crypto.sign(self.key, SIGNATURE_TEST_DATA, 'sha256')
|
||||
# Verify wants a cert (where it can get the public key from)
|
||||
cert = crypto.X509()
|
||||
cert.set_pubkey(self.key)
|
||||
try:
|
||||
crypto.verify(cert, signature, SIGNATURE_TEST_DATA, 'sha256')
|
||||
return True
|
||||
except crypto.Error:
|
||||
return False
|
||||
# If needed and if possible, fall back to cryptography
|
||||
if PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND:
|
||||
return _is_cryptography_key_consistent(self.key.to_cryptography_key(), key_public_data, key_private_data)
|
||||
return None
|
||||
|
||||
|
||||
def get_privatekey_info(module, backend, content, passphrase=None, return_private_key_data=False, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = PrivateKeyInfoRetrievalCryptography(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
elif backend == 'pyopenssl':
|
||||
info = PrivateKeyInfoRetrievalPyOpenSSL(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
def select_backend(module, backend, content, passphrase=None, return_private_key_data=False):
|
||||
def select_backend(module, backend, content, passphrase=None, return_private_key_data=False, check_consistency=False):
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
module.fail_json(msg=("Cannot detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, PrivateKeyInfoRetrievalPyOpenSSL(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
return backend, PrivateKeyInfoRetrievalCryptography(
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
|
||||
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data, check_consistency=check_consistency)
|
||||
else:
|
||||
raise ValueError('Unsupported value for backend: {0}'.format(backend))
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2020-2021, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# Copyright (c) 2020-2021, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -31,18 +32,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_PYOPENSSL_VERSION = '16.0.0' # when working with public key objects, the minimal required version is 0.15
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -93,98 +82,6 @@ def _get_cryptography_public_key_info(key):
|
||||
return key_type, key_public_data
|
||||
|
||||
|
||||
def _bigint_to_int(bn):
|
||||
'''Convert OpenSSL BIGINT to Python integer'''
|
||||
if bn == OpenSSL._util.ffi.NULL:
|
||||
return None
|
||||
hexstr = OpenSSL._util.lib.BN_bn2hex(bn)
|
||||
try:
|
||||
return int(OpenSSL._util.ffi.string(hexstr), 16)
|
||||
finally:
|
||||
OpenSSL._util.lib.OPENSSL_free(hexstr)
|
||||
|
||||
|
||||
def _get_pyopenssl_public_key_info(key):
|
||||
key_public_data = dict()
|
||||
try_fallback = True
|
||||
openssl_key_type = key.type()
|
||||
if crypto.TYPE_RSA == openssl_key_type:
|
||||
key_type = 'RSA'
|
||||
key_public_data['size'] = key.bits()
|
||||
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_RSA(key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.RSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get modulus and exponents
|
||||
n = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
e = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
d = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.RSA_get0_key(key, n, e, d)
|
||||
key_public_data['modulus'] = _bigint_to_int(n[0])
|
||||
key_public_data['exponent'] = _bigint_to_int(e[0])
|
||||
else:
|
||||
# Get modulus and exponents
|
||||
key_public_data['modulus'] = _bigint_to_int(key.n)
|
||||
key_public_data['exponent'] = _bigint_to_int(key.e)
|
||||
try_fallback = False
|
||||
except AttributeError:
|
||||
# Use fallback if available
|
||||
pass
|
||||
elif crypto.TYPE_DSA == openssl_key_type:
|
||||
key_type = 'DSA'
|
||||
key_public_data['size'] = key.bits()
|
||||
|
||||
try:
|
||||
# Use OpenSSL directly to extract key data
|
||||
key = OpenSSL._util.lib.EVP_PKEY_get1_DSA(key._pkey)
|
||||
key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.DSA_free)
|
||||
# OpenSSL 1.1 and newer have functions to extract the parameters
|
||||
# from the EVP PKEY data structures. Older versions didn't have
|
||||
# these getters, and it was common use to simply access the values
|
||||
# directly. Since there's no guarantee that these data structures
|
||||
# will still be accessible in the future, we use the getters for
|
||||
# 1.1 and later, and directly access the values for 1.0.x and
|
||||
# earlier.
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# Get public parameters (primes and group element)
|
||||
p = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
q = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
g = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.DSA_get0_pqg(key, p, q, g)
|
||||
key_public_data['p'] = _bigint_to_int(p[0])
|
||||
key_public_data['q'] = _bigint_to_int(q[0])
|
||||
key_public_data['g'] = _bigint_to_int(g[0])
|
||||
# Get public key exponents
|
||||
y = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
x = OpenSSL._util.ffi.new("BIGNUM **")
|
||||
OpenSSL._util.lib.DSA_get0_key(key, y, x)
|
||||
key_public_data['y'] = _bigint_to_int(y[0])
|
||||
else:
|
||||
# Get public parameters (primes and group element)
|
||||
key_public_data['p'] = _bigint_to_int(key.p)
|
||||
key_public_data['q'] = _bigint_to_int(key.q)
|
||||
key_public_data['g'] = _bigint_to_int(key.g)
|
||||
# Get public key exponents
|
||||
key_public_data['y'] = _bigint_to_int(key.pub_key)
|
||||
try_fallback = False
|
||||
except AttributeError:
|
||||
# Use fallback if available
|
||||
pass
|
||||
else:
|
||||
# Return 'unknown'
|
||||
key_type = 'unknown ({0})'.format(key.type())
|
||||
return key_type, key_public_data, try_fallback
|
||||
|
||||
|
||||
class PublicKeyParseError(OpenSSLObjectError):
|
||||
def __init__(self, msg, result):
|
||||
super(PublicKeyParseError, self).__init__(msg)
|
||||
@@ -242,46 +139,9 @@ class PublicKeyInfoRetrievalCryptography(PublicKeyInfoRetrieval):
|
||||
return _get_cryptography_public_key_info(self.key)
|
||||
|
||||
|
||||
class PublicKeyInfoRetrievalPyOpenSSL(PublicKeyInfoRetrieval):
|
||||
"""validate the supplied public key."""
|
||||
|
||||
def __init__(self, module, content=None, key=None):
|
||||
super(PublicKeyInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content=content, key=key)
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
|
||||
self.key
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
if binary:
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.key._pkey)
|
||||
else:
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.key._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
return crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
|
||||
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
|
||||
|
||||
def _get_key_info(self):
|
||||
key_type, key_public_data, try_fallback = _get_pyopenssl_public_key_info(self.key)
|
||||
# If needed and if possible, fall back to cryptography
|
||||
if try_fallback and PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND:
|
||||
return _get_cryptography_public_key_info(self.key.to_cryptography_key())
|
||||
return key_type, key_public_data
|
||||
|
||||
|
||||
def get_publickey_info(module, backend, content=None, key=None, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = PublicKeyInfoRetrievalCryptography(module, content=content, key=key)
|
||||
elif backend == 'pyopenssl':
|
||||
info = PublicKeyInfoRetrievalPyOpenSSL(module, content=content, key=key)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
@@ -289,29 +149,17 @@ def select_backend(module, backend, content=None, key=None):
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
module.fail_json(msg=("Cannot detect any of the required Python libraries "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, PublicKeyInfoRetrievalPyOpenSSL(module, content=content, key=key)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2020, Doug Stanley <doug+ansible@technologixllc.com>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2020, Doug Stanley <doug+ansible@technologixllc.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
# This import is only to maintain backwards compatibility
|
||||
from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import (
|
||||
from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import ( # noqa: F401, pylint: disable=unused-import
|
||||
parse_openssh_version
|
||||
)
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import base64
|
||||
|
||||
from ansible.module_utils.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 ._objects import OID_LOOKUP
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
except (ImportError, AttributeError):
|
||||
# Error handled in the calling module.
|
||||
pass
|
||||
|
||||
from ._objects import (
|
||||
NORMALIZE_NAMES_SHORT,
|
||||
NORMALIZE_NAMES,
|
||||
)
|
||||
|
||||
from ._obj2txt import obj2txt
|
||||
|
||||
from .basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
|
||||
def pyopenssl_normalize_name(name, short=False):
|
||||
nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(name))
|
||||
if nid != 0:
|
||||
b_name = OpenSSL._util.lib.OBJ_nid2ln(nid)
|
||||
name = to_text(OpenSSL._util.ffi.string(b_name))
|
||||
if short:
|
||||
return NORMALIZE_NAMES_SHORT.get(name, name)
|
||||
else:
|
||||
return NORMALIZE_NAMES.get(name, name)
|
||||
|
||||
|
||||
def pyopenssl_normalize_name_attribute(san):
|
||||
# apparently openssl returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
|
||||
# although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
|
||||
if san.startswith('IP Address:'):
|
||||
san = 'IP:' + san[len('IP Address:'):]
|
||||
if san.startswith('IP:'):
|
||||
address = san[3:]
|
||||
if '/' in address:
|
||||
ip = compat_ipaddress.ip_network(address)
|
||||
san = 'IP:{0}/{1}'.format(ip.network_address.compressed, ip.prefixlen)
|
||||
else:
|
||||
ip = compat_ipaddress.ip_address(address)
|
||||
san = 'IP:{0}'.format(ip.compressed)
|
||||
if san.startswith('Registered ID:'):
|
||||
san = 'RID:' + san[len('Registered ID:'):]
|
||||
# Some versions of OpenSSL apparently forgot the colon. Happens in CI with Ubuntu 16.04 and FreeBSD 11.1
|
||||
if san.startswith('Registered ID'):
|
||||
san = 'RID:' + san[len('Registered ID'):]
|
||||
return san
|
||||
|
||||
|
||||
def pyopenssl_get_extensions_from_cert(cert):
|
||||
# While pyOpenSSL allows us to get an extension's DER value, it won't
|
||||
# give us the dotted string for an OID. So we have to do some magic to
|
||||
# get hold of it.
|
||||
result = dict()
|
||||
ext_count = cert.get_extension_count()
|
||||
for i in range(0, ext_count):
|
||||
ext = cert.get_extension(i)
|
||||
entry = dict(
|
||||
critical=bool(ext.get_critical()),
|
||||
value=base64.b64encode(ext.get_data()),
|
||||
)
|
||||
try:
|
||||
oid = obj2txt(
|
||||
OpenSSL._util.lib,
|
||||
OpenSSL._util.ffi,
|
||||
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
|
||||
)
|
||||
# This could also be done a bit simpler:
|
||||
#
|
||||
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
|
||||
#
|
||||
# Unfortunately this gives the wrong result in case the linked OpenSSL
|
||||
# doesn't know the OID. That's why we have to get the OID dotted string
|
||||
# similarly to how cryptography does it.
|
||||
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
|
||||
return result
|
||||
|
||||
|
||||
def pyopenssl_get_extensions_from_csr(csr):
|
||||
# While pyOpenSSL allows us to get an extension's DER value, it won't
|
||||
# give us the dotted string for an OID. So we have to do some magic to
|
||||
# get hold of it.
|
||||
result = dict()
|
||||
for ext in csr.get_extensions():
|
||||
entry = dict(
|
||||
critical=bool(ext.get_critical()),
|
||||
value=base64.b64encode(ext.get_data()),
|
||||
)
|
||||
try:
|
||||
oid = obj2txt(
|
||||
OpenSSL._util.lib,
|
||||
OpenSSL._util.ffi,
|
||||
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
|
||||
)
|
||||
# This could also be done a bit simpler:
|
||||
#
|
||||
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
|
||||
#
|
||||
# Unfortunately this gives the wrong result in case the linked OpenSSL
|
||||
# doesn't know the OID. That's why we have to get the OID dotted string
|
||||
# similarly to how cryptography does it.
|
||||
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
|
||||
return result
|
||||
|
||||
|
||||
def pyopenssl_parse_name_constraints(name_constraints_extension):
|
||||
lines = to_text(name_constraints_extension, errors='surrogate_or_strict').splitlines()
|
||||
exclude = None
|
||||
excluded = []
|
||||
permitted = []
|
||||
for line in lines:
|
||||
if line.startswith(' ') or line.startswith('\t'):
|
||||
name = pyopenssl_normalize_name_attribute(line.strip())
|
||||
if exclude is True:
|
||||
excluded.append(name)
|
||||
elif exclude is False:
|
||||
permitted.append(name)
|
||||
else:
|
||||
raise OpenSSLObjectError('Unexpected nameConstraint line: "{0}"'.format(line))
|
||||
else:
|
||||
line_lc = line.lower()
|
||||
if line_lc.startswith('exclud'):
|
||||
exclude = True
|
||||
elif line_lc.startswith('includ') or line_lc.startswith('permitt'):
|
||||
exclude = False
|
||||
else:
|
||||
raise OpenSSLObjectError('Cannot parse nameConstraint line: "{0}"'.format(line))
|
||||
return permitted, excluded
|
||||
@@ -1,19 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
@@ -29,6 +18,10 @@ import re
|
||||
from ansible.module_utils import six
|
||||
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||
identify_pem_format,
|
||||
)
|
||||
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
HAS_PYOPENSSL = True
|
||||
@@ -98,25 +91,10 @@ def get_fingerprint_of_bytes(source, prefer_one=False):
|
||||
return fingerprint
|
||||
|
||||
|
||||
def get_fingerprint_of_privatekey(privatekey, backend='pyopenssl', prefer_one=False):
|
||||
def get_fingerprint_of_privatekey(privatekey, backend='cryptography', prefer_one=False):
|
||||
"""Generate the fingerprint of the public key. """
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
try:
|
||||
publickey = crypto.dump_publickey(crypto.FILETYPE_ASN1, privatekey)
|
||||
except AttributeError:
|
||||
# If PyOpenSSL < 16.0 crypto.dump_publickey() will fail.
|
||||
try:
|
||||
bio = crypto._new_mem_buf()
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, privatekey._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
publickey = crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
# By doing this we prevent the code from raising an error
|
||||
# yet we return no value in the fingerprint hash.
|
||||
return None
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
publickey = privatekey.public_key().public_bytes(
|
||||
serialization.Encoding.DER,
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
@@ -125,7 +103,7 @@ def get_fingerprint_of_privatekey(privatekey, backend='pyopenssl', prefer_one=Fa
|
||||
return get_fingerprint_of_bytes(publickey, prefer_one=prefer_one)
|
||||
|
||||
|
||||
def get_fingerprint(path, passphrase=None, content=None, backend='pyopenssl', prefer_one=False):
|
||||
def get_fingerprint(path, passphrase=None, content=None, backend='cryptography', prefer_one=False):
|
||||
"""Generate the fingerprint of the public key. """
|
||||
|
||||
privatekey = load_privatekey(path, passphrase=passphrase, content=content, check_passphrase=False, backend=backend)
|
||||
@@ -133,7 +111,7 @@ def get_fingerprint(path, passphrase=None, content=None, backend='pyopenssl', pr
|
||||
return get_fingerprint_of_privatekey(privatekey, backend=backend, prefer_one=prefer_one)
|
||||
|
||||
|
||||
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='pyopenssl'):
|
||||
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='cryptography'):
|
||||
"""Load the specified OpenSSL private key.
|
||||
|
||||
The content can also be specified via content; in that case,
|
||||
@@ -177,13 +155,13 @@ def load_privatekey(path, passphrase=None, check_passphrase=True, content=None,
|
||||
to_bytes('y' if passphrase == 'x' else 'x'))
|
||||
if passphrase is not None:
|
||||
# Since we can load the key without an exception, the
|
||||
# key isn't password-protected
|
||||
# key is not password-protected
|
||||
raise OpenSSLBadPassphraseError('Passphrase provided, but private key is not password-protected!')
|
||||
except crypto.Error as e:
|
||||
if passphrase is None and len(e.args) > 0 and len(e.args[0]) > 0:
|
||||
if e.args[0][0][2] in ('bad decrypt', 'bad password read'):
|
||||
# The key is obviously protected by the empty string.
|
||||
# Don't do this at home (if it's possible at all)...
|
||||
# Do not do this at home (if it's possible at all)...
|
||||
raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!')
|
||||
elif backend == 'cryptography':
|
||||
try:
|
||||
@@ -213,14 +191,9 @@ def load_publickey(path=None, content=None, backend=None):
|
||||
return serialization.load_pem_public_key(content, backend=cryptography_backend())
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
|
||||
else:
|
||||
try:
|
||||
return crypto.load_publickey(crypto.FILETYPE_PEM, content)
|
||||
except crypto.Error as e:
|
||||
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
|
||||
|
||||
|
||||
def load_certificate(path, content=None, backend='pyopenssl'):
|
||||
def load_certificate(path, content=None, backend='cryptography', der_support_enabled=False):
|
||||
"""Load the specified certificate."""
|
||||
|
||||
try:
|
||||
@@ -232,15 +205,24 @@ def load_certificate(path, content=None, backend='pyopenssl'):
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
if backend == 'pyopenssl':
|
||||
return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content)
|
||||
if der_support_enabled is False or identify_pem_format(cert_content):
|
||||
return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content)
|
||||
elif der_support_enabled:
|
||||
raise OpenSSLObjectError('Certificate in DER format is not supported by the pyopenssl backend.')
|
||||
elif backend == 'cryptography':
|
||||
try:
|
||||
return x509.load_pem_x509_certificate(cert_content, cryptography_backend())
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
if der_support_enabled is False or identify_pem_format(cert_content):
|
||||
try:
|
||||
return x509.load_pem_x509_certificate(cert_content, cryptography_backend())
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
elif der_support_enabled:
|
||||
try:
|
||||
return x509.load_der_x509_certificate(cert_content, cryptography_backend())
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError('Cannot parse DER certificate: {0}'.format(exc))
|
||||
|
||||
|
||||
def load_certificate_request(path, content=None, backend='pyopenssl'):
|
||||
def load_certificate_request(path, content=None, backend='cryptography'):
|
||||
"""Load the specified certificate signing request."""
|
||||
try:
|
||||
if content is None:
|
||||
@@ -250,25 +232,50 @@ def load_certificate_request(path, content=None, backend='pyopenssl'):
|
||||
csr_content = content
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
if backend == 'pyopenssl':
|
||||
return crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_content)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
try:
|
||||
return x509.load_pem_x509_csr(csr_content, cryptography_backend())
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
|
||||
|
||||
def parse_name_field(input_dict):
|
||||
def parse_name_field(input_dict, name_field_name=None):
|
||||
"""Take a dict with key: value or key: list_of_values mappings and return a list of tuples"""
|
||||
error_str = '{key}' if name_field_name is None else '{key} in {name}'
|
||||
|
||||
result = []
|
||||
for key, value in input_dict.items():
|
||||
if isinstance(value, list):
|
||||
for entry in value:
|
||||
if not isinstance(entry, six.string_types):
|
||||
raise TypeError(('Values %s must be strings' % error_str).format(key=key, name=name_field_name))
|
||||
if not entry:
|
||||
raise ValueError(('Values for %s must not be empty strings' % error_str).format(key=key))
|
||||
result.append((key, entry))
|
||||
elif isinstance(value, six.string_types):
|
||||
if not value:
|
||||
raise ValueError(('Value for %s must not be an empty string' % error_str).format(key=key))
|
||||
result.append((key, value))
|
||||
else:
|
||||
raise TypeError(('Value for %s must be either a string or a list of strings' % error_str).format(key=key))
|
||||
return result
|
||||
|
||||
|
||||
def parse_ordered_name_field(input_list, name_field_name):
|
||||
"""Take a dict with key: value or key: list_of_values mappings and return a list of tuples"""
|
||||
|
||||
result = []
|
||||
for key in input_dict:
|
||||
if isinstance(input_dict[key], list):
|
||||
for entry in input_dict[key]:
|
||||
result.append((key, entry))
|
||||
else:
|
||||
result.append((key, input_dict[key]))
|
||||
for index, entry in enumerate(input_list):
|
||||
if len(entry) != 1:
|
||||
raise ValueError(
|
||||
'Entry #{index} in {name} must be a dictionary with exactly one key-value pair'.format(
|
||||
name=name_field_name, index=index + 1))
|
||||
try:
|
||||
result.extend(parse_name_field(entry, name_field_name=name_field_name))
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(
|
||||
'Error while processing entry #{index} in {name}: {error}'.format(
|
||||
name=name_field_name, index=index + 1, error=exc))
|
||||
return result
|
||||
|
||||
|
||||
@@ -322,9 +329,7 @@ def get_relative_time_option(input_string, input_name, backend='cryptography'):
|
||||
elif backend == 'cryptography':
|
||||
return result_datetime
|
||||
# Absolute time
|
||||
if backend == 'pyopenssl':
|
||||
return input_string
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
for date_fmt in ['%Y%m%d%H%M%SZ', '%Y%m%d%H%MZ', '%Y%m%d%H%M%S%z', '%Y%m%d%H%M%z']:
|
||||
try:
|
||||
return datetime.datetime.strptime(result, date_fmt)
|
||||
|
||||
@@ -7,25 +7,8 @@
|
||||
# their own license to the complete work.
|
||||
#
|
||||
# Copyright (c), Entrust Datacard Corporation, 2019
|
||||
# Simplified BSD License (see simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
@@ -34,7 +17,6 @@ __metaclass__ = type
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text, to_native
|
||||
@@ -105,7 +87,7 @@ def bind(instance, method, operation_spec):
|
||||
def binding_scope_fn(*args, **kwargs):
|
||||
return method(instance, *args, **kwargs)
|
||||
|
||||
# Make sure we don't confuse users; add the proper name and documentation to the function.
|
||||
# Make sure we do not confuse users; add the proper name and documentation to the function.
|
||||
# Users can use !help(<function>) to get help on the function from interactive python or pdb
|
||||
operation_name = operation_spec.get("operationId").split("Using")[0]
|
||||
binding_scope_fn.__name__ = str(operation_name)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user