mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7c0a85c72 | ||
|
|
8935ab8fdc | ||
|
|
1f39b0ff2a | ||
|
|
b02fb8e9a0 | ||
|
|
d50c3cc944 | ||
|
|
4c26fada5e | ||
|
|
d13d1868b6 | ||
|
|
6a0953b19f | ||
|
|
6ba06f24ce | ||
|
|
577d86265e | ||
|
|
1c1b59b719 | ||
|
|
518847a92c | ||
|
|
aa30b4c803 | ||
|
|
a9dab608c7 | ||
|
|
e6643fd2dd | ||
|
|
f58606b64d | ||
|
|
5e60bee9c0 | ||
|
|
33410b1d57 | ||
|
|
e365ae3226 | ||
|
|
5f6e0095b0 | ||
|
|
dc052bee21 | ||
|
|
38849514f3 | ||
|
|
7810e2c3bf | ||
|
|
5d4cbbb038 | ||
|
|
58a81374d6 | ||
|
|
c29c34bab2 | ||
|
|
b4452d4be1 | ||
|
|
7fc3ad0263 | ||
|
|
65ea02a73d | ||
|
|
00d23753ca | ||
|
|
3d8c68e189 | ||
|
|
d7a0723a52 | ||
|
|
67bf3a7991 | ||
|
|
82251c2d80 | ||
|
|
f43fa94549 | ||
|
|
29ac3cbe81 | ||
|
|
5e59c5261e | ||
|
|
aa82575a78 | ||
|
|
f3c9cb7a8a | ||
|
|
f82b335916 | ||
|
|
553ab45f46 | ||
|
|
59606d48ad | ||
|
|
0a15be1017 | ||
|
|
9501a28a93 | ||
|
|
d906914737 | ||
|
|
33d278ad8f | ||
|
|
6d4fc589ae | ||
|
|
9614b09f7a | ||
|
|
af5f4b57f8 | ||
|
|
c6fbe58382 | ||
|
|
afe7f7522c | ||
|
|
0c62837296 | ||
|
|
d71637c77d | ||
|
|
3899f79f97 | ||
|
|
8ce0051f9b | ||
|
|
4be691da50 | ||
|
|
8fe012cf09 | ||
|
|
27a9ff14fb | ||
|
|
ae548de502 | ||
|
|
1b75f1aa9c | ||
|
|
7e33398d5c | ||
|
|
50c2c4db29 | ||
|
|
ee0ceea118 | ||
|
|
b98cec74ae | ||
|
|
05cc5fe82b | ||
|
|
fad3c1352b | ||
|
|
4167d2c4b3 | ||
|
|
ff1504dc58 | ||
|
|
08adb6b297 | ||
|
|
42ba0a88f4 | ||
|
|
1736602ce7 | ||
|
|
6b1a3d6e68 | ||
|
|
51591891d3 | ||
|
|
d1a229c255 | ||
|
|
d9698a6eff | ||
|
|
37fed289e6 | ||
|
|
9ec8680936 | ||
|
|
87af1f2761 | ||
|
|
da30487119 | ||
|
|
b57aa4a2ca | ||
|
|
a5f5ea1128 | ||
|
|
91dd7cd4dc | ||
|
|
2913826352 | ||
|
|
0bc15598d7 | ||
|
|
fb3f68ca96 | ||
|
|
a4edf22a9c | ||
|
|
97e44c4ba5 | ||
|
|
453adb5d04 | ||
|
|
033b456b7a | ||
|
|
73dbb84fc6 | ||
|
|
780fb28946 | ||
|
|
815ce43d17 | ||
|
|
170d837122 | ||
|
|
b5269b25a3 | ||
|
|
f12e814344 | ||
|
|
5d5a21fddf | ||
|
|
67f1d1129b | ||
|
|
d9362a2ce9 | ||
|
|
4e5966e477 | ||
|
|
22e24f24c6 | ||
|
|
35b47f73f4 | ||
|
|
9cc1731767 | ||
|
|
c592eaa35a | ||
|
|
525a8a5df4 | ||
|
|
e4ba0861e5 | ||
|
|
29cd0b3bde | ||
|
|
f2ebae635a | ||
|
|
75934cdd8c | ||
|
|
cf1fe027dd | ||
|
|
e9dbc1a5a5 | ||
|
|
6bd5eee9b0 | ||
|
|
fc707c7e31 | ||
|
|
eba7e32df1 | ||
|
|
6504e67139 | ||
|
|
428550165a | ||
|
|
a150e77507 | ||
|
|
d1299c11d6 | ||
|
|
fccc9d32ee | ||
|
|
d63c195bff | ||
|
|
e7515584b1 | ||
|
|
0d010968e5 | ||
|
|
5f4fc95c50 | ||
|
|
b2a92ef0bf | ||
|
|
01cdc4a572 | ||
|
|
cdfc881b32 | ||
|
|
d7293aa1cd | ||
|
|
1e78918ad3 | ||
|
|
526b3c4393 | ||
|
|
5d2bfddc15 | ||
|
|
5ac603bbcc | ||
|
|
e41a50af97 | ||
|
|
d3737f5ef7 | ||
|
|
addbd067c8 | ||
|
|
62c842548d | ||
|
|
5526fcac27 | ||
|
|
55c94eb5c0 | ||
|
|
e64d617de6 | ||
|
|
ba456c5eaf | ||
|
|
5e630ffe78 | ||
|
|
9ae75d4840 | ||
|
|
78eeb1219a | ||
|
|
54b2163c56 | ||
|
|
1ca0d2f21d | ||
|
|
2a789f8b01 | ||
|
|
cffba005f0 | ||
|
|
6c72734652 | ||
|
|
83af72a3bc | ||
|
|
ed6285e083 | ||
|
|
57a8c7e652 | ||
|
|
b40a1c54f7 | ||
|
|
8fa4dc75c9 | ||
|
|
99d1521266 | ||
|
|
c78536dfeb | ||
|
|
288dc5be2c | ||
|
|
9ae28e2fab | ||
|
|
f27b66baa3 | ||
|
|
230f0b51f2 | ||
|
|
1f84d0a317 | ||
|
|
2f64d42855 | ||
|
|
9c07a8354e | ||
|
|
a7e9bb7618 | ||
|
|
ad118bbbd6 | ||
|
|
d823382732 | ||
|
|
3a5d9129b2 | ||
|
|
17702d1a76 | ||
|
|
9305bfe190 | ||
|
|
0d30a3793a | ||
|
|
a402c485a3 | ||
|
|
05ad2e5008 | ||
|
|
e3bc22f7d5 | ||
|
|
c703dd6056 | ||
|
|
153de3ffef | ||
|
|
3bcc0db4fc | ||
|
|
142403c6cb | ||
|
|
a2d4554c78 | ||
|
|
a89fd2733b |
@@ -46,7 +46,7 @@ variables:
|
|||||||
resources:
|
resources:
|
||||||
containers:
|
containers:
|
||||||
- container: default
|
- container: default
|
||||||
image: quay.io/ansible/azure-pipelines-test-container:3.0.0
|
image: quay.io/ansible/azure-pipelines-test-container:6.0.0
|
||||||
|
|
||||||
pool: Standard
|
pool: Standard
|
||||||
|
|
||||||
@@ -65,6 +65,28 @@ stages:
|
|||||||
test: 'devel/sanity/extra'
|
test: 'devel/sanity/extra'
|
||||||
- name: Units
|
- name: Units
|
||||||
test: 'devel/units/1'
|
test: 'devel/units/1'
|
||||||
|
- stage: Ansible_2_17
|
||||||
|
displayName: Sanity & Units 2.17
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
targets:
|
||||||
|
- name: Sanity
|
||||||
|
test: '2.17/sanity/1'
|
||||||
|
- name: Units
|
||||||
|
test: '2.17/units/1'
|
||||||
|
- stage: Ansible_2_16
|
||||||
|
displayName: Sanity & Units 2.16
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
targets:
|
||||||
|
- name: Sanity
|
||||||
|
test: '2.16/sanity/1'
|
||||||
|
- name: Units
|
||||||
|
test: '2.16/units/1'
|
||||||
- stage: Ansible_2_15
|
- stage: Ansible_2_15
|
||||||
displayName: Sanity & Units 2.15
|
displayName: Sanity & Units 2.15
|
||||||
dependsOn: []
|
dependsOn: []
|
||||||
@@ -76,39 +98,6 @@ stages:
|
|||||||
test: '2.15/sanity/1'
|
test: '2.15/sanity/1'
|
||||||
- name: Units
|
- name: Units
|
||||||
test: '2.15/units/1'
|
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: []
|
|
||||||
jobs:
|
|
||||||
- template: templates/matrix.yml
|
|
||||||
parameters:
|
|
||||||
targets:
|
|
||||||
- name: Sanity
|
|
||||||
test: '2.13/sanity/1'
|
|
||||||
- name: Units
|
|
||||||
test: '2.13/units/1'
|
|
||||||
- stage: Ansible_2_12
|
|
||||||
displayName: Sanity & Units 2.12
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
|
||||||
- template: templates/matrix.yml
|
|
||||||
parameters:
|
|
||||||
targets:
|
|
||||||
- name: Sanity
|
|
||||||
test: '2.12/sanity/1'
|
|
||||||
- name: Units
|
|
||||||
test: '2.12/units/1'
|
|
||||||
### Docker
|
### Docker
|
||||||
- stage: Docker_devel
|
- stage: Docker_devel
|
||||||
displayName: Docker devel
|
displayName: Docker devel
|
||||||
@@ -118,14 +107,44 @@ stages:
|
|||||||
parameters:
|
parameters:
|
||||||
testFormat: devel/linux/{0}
|
testFormat: devel/linux/{0}
|
||||||
targets:
|
targets:
|
||||||
- name: Fedora 37
|
- name: Fedora 40
|
||||||
test: fedora37
|
test: fedora40
|
||||||
- name: openSUSE 15
|
- name: Ubuntu 24.04
|
||||||
test: opensuse15
|
test: ubuntu2404
|
||||||
- name: Ubuntu 20.04
|
- name: Alpine 3.20
|
||||||
test: ubuntu2004
|
test: alpine320
|
||||||
|
groups:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- stage: Docker_2_17
|
||||||
|
displayName: Docker 2.17
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
testFormat: 2.17/linux/{0}
|
||||||
|
targets:
|
||||||
|
- name: Fedora 39
|
||||||
|
test: fedora39
|
||||||
- name: Ubuntu 22.04
|
- name: Ubuntu 22.04
|
||||||
test: ubuntu2204
|
test: ubuntu2204
|
||||||
|
- name: Alpine 3.19
|
||||||
|
test: alpine319
|
||||||
|
groups:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- stage: Docker_2_16
|
||||||
|
displayName: Docker 2.16
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
testFormat: 2.16/linux/{0}
|
||||||
|
targets:
|
||||||
|
- name: Fedora 38
|
||||||
|
test: fedora38
|
||||||
|
- name: openSUSE 15
|
||||||
|
test: opensuse15
|
||||||
- name: Alpine 3
|
- name: Alpine 3
|
||||||
test: alpine3
|
test: alpine3
|
||||||
groups:
|
groups:
|
||||||
@@ -139,60 +158,13 @@ stages:
|
|||||||
parameters:
|
parameters:
|
||||||
testFormat: 2.15/linux/{0}
|
testFormat: 2.15/linux/{0}
|
||||||
targets:
|
targets:
|
||||||
|
- name: Fedora 37
|
||||||
|
test: fedora37
|
||||||
- name: CentOS 7
|
- name: CentOS 7
|
||||||
test: centos7
|
test: centos7
|
||||||
groups:
|
groups:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 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}
|
|
||||||
targets:
|
|
||||||
- name: openSUSE 15 py2
|
|
||||||
test: opensuse15py2
|
|
||||||
- name: Fedora 35
|
|
||||||
test: fedora35
|
|
||||||
- name: Fedora 34
|
|
||||||
test: fedora34
|
|
||||||
- name: Ubuntu 18.04
|
|
||||||
test: ubuntu1804
|
|
||||||
- name: Alpine 3
|
|
||||||
test: alpine3
|
|
||||||
groups:
|
|
||||||
- 1
|
|
||||||
- 2
|
|
||||||
- stage: Docker_2_12
|
|
||||||
displayName: Docker 2.12
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
|
||||||
- template: templates/matrix.yml
|
|
||||||
parameters:
|
|
||||||
testFormat: 2.12/linux/{0}
|
|
||||||
targets:
|
|
||||||
- name: CentOS 6
|
|
||||||
test: centos6
|
|
||||||
- name: Fedora 33
|
|
||||||
test: fedora33
|
|
||||||
groups:
|
|
||||||
- 1
|
|
||||||
- 2
|
|
||||||
|
|
||||||
### Community Docker
|
### Community Docker
|
||||||
- stage: Docker_community_devel
|
- stage: Docker_community_devel
|
||||||
@@ -205,12 +177,10 @@ stages:
|
|||||||
targets:
|
targets:
|
||||||
- name: Debian Bullseye
|
- name: Debian Bullseye
|
||||||
test: debian-bullseye/3.9
|
test: debian-bullseye/3.9
|
||||||
|
- name: Debian Bookworm
|
||||||
|
test: debian-bookworm/3.11
|
||||||
- name: ArchLinux
|
- name: ArchLinux
|
||||||
test: archlinux/3.10
|
test: archlinux/3.12
|
||||||
- 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:
|
groups:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
@@ -224,14 +194,14 @@ stages:
|
|||||||
parameters:
|
parameters:
|
||||||
testFormat: devel/{0}
|
testFormat: devel/{0}
|
||||||
targets:
|
targets:
|
||||||
- name: Alpine 3.17
|
- name: Alpine 3.20
|
||||||
test: alpine/3.17
|
test: alpine/3.20
|
||||||
- name: Fedora 37
|
- name: Fedora 40
|
||||||
test: fedora/37
|
test: fedora/40
|
||||||
- name: Ubuntu 20.04
|
|
||||||
test: ubuntu/20.04
|
|
||||||
- name: Ubuntu 22.04
|
- name: Ubuntu 22.04
|
||||||
test: ubuntu/22.04
|
test: ubuntu/22.04
|
||||||
|
- name: Ubuntu 24.04
|
||||||
|
test: ubuntu/24.04
|
||||||
groups:
|
groups:
|
||||||
- vm
|
- vm
|
||||||
- stage: Remote_devel
|
- stage: Remote_devel
|
||||||
@@ -241,15 +211,49 @@ stages:
|
|||||||
- template: templates/matrix.yml
|
- template: templates/matrix.yml
|
||||||
parameters:
|
parameters:
|
||||||
testFormat: devel/{0}
|
testFormat: devel/{0}
|
||||||
|
targets:
|
||||||
|
- name: macOS 14.3
|
||||||
|
test: macos/14.3
|
||||||
|
- name: RHEL 9.4
|
||||||
|
test: rhel/9.4
|
||||||
|
- name: FreeBSD 14.1
|
||||||
|
test: freebsd/14.1
|
||||||
|
groups:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- stage: Remote_2_17
|
||||||
|
displayName: Remote 2.17
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
testFormat: 2.17/{0}
|
||||||
|
targets:
|
||||||
|
- name: RHEL 9.3
|
||||||
|
test: rhel/9.3
|
||||||
|
- name: FreeBSD 13.3
|
||||||
|
test: freebsd/13.3
|
||||||
|
- name: FreeBSD 14.0
|
||||||
|
test: freebsd/14.0
|
||||||
|
groups:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- stage: Remote_2_16
|
||||||
|
displayName: Remote 2.16
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
testFormat: 2.16/{0}
|
||||||
targets:
|
targets:
|
||||||
- name: macOS 13.2
|
- name: macOS 13.2
|
||||||
test: macos/13.2
|
test: macos/13.2
|
||||||
- name: RHEL 9.1
|
- name: RHEL 9.2
|
||||||
test: rhel/9.1
|
test: rhel/9.2
|
||||||
- name: FreeBSD 12.4
|
- name: RHEL 8.8
|
||||||
test: freebsd/12.4
|
test: rhel/8.8
|
||||||
- name: FreeBSD 13.1
|
# - name: FreeBSD 13.2
|
||||||
test: freebsd/13.1
|
# test: freebsd/13.2
|
||||||
groups:
|
groups:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
@@ -261,58 +265,16 @@ stages:
|
|||||||
parameters:
|
parameters:
|
||||||
testFormat: 2.15/{0}
|
testFormat: 2.15/{0}
|
||||||
targets:
|
targets:
|
||||||
|
- name: RHEL 9.1
|
||||||
|
test: rhel/9.1
|
||||||
|
- name: RHEL 8.7
|
||||||
|
test: rhel/8.7
|
||||||
- name: RHEL 7.9
|
- name: RHEL 7.9
|
||||||
test: rhel/7.9
|
test: rhel/7.9
|
||||||
groups:
|
# - name: FreeBSD 13.1
|
||||||
- 1
|
# test: freebsd/13.1
|
||||||
- 2
|
# - name: FreeBSD 12.4
|
||||||
- stage: Remote_2_14
|
# test: freebsd/12.4
|
||||||
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}
|
|
||||||
targets:
|
|
||||||
- name: RHEL 8.5
|
|
||||||
test: rhel/8.5
|
|
||||||
- name: FreeBSD 13.0
|
|
||||||
test: freebsd/13.0
|
|
||||||
groups:
|
|
||||||
- 1
|
|
||||||
- 2
|
|
||||||
- stage: Remote_2_12
|
|
||||||
displayName: Remote 2.12
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
|
||||||
- template: templates/matrix.yml
|
|
||||||
parameters:
|
|
||||||
testFormat: 2.12/{0}
|
|
||||||
targets:
|
|
||||||
# Not working anymore:
|
|
||||||
# - name: macOS 11.1
|
|
||||||
# test: macos/11.1
|
|
||||||
- name: RHEL 8.4
|
|
||||||
test: rhel/8.4
|
|
||||||
- name: FreeBSD 12.2
|
|
||||||
test: freebsd/12.2
|
|
||||||
groups:
|
groups:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
@@ -326,14 +288,40 @@ stages:
|
|||||||
nameFormat: Python {0}
|
nameFormat: Python {0}
|
||||||
testFormat: devel/generic/{0}
|
testFormat: devel/generic/{0}
|
||||||
targets:
|
targets:
|
||||||
- test: 2.7
|
- test: 3.8
|
||||||
- test: 3.5
|
|
||||||
- test: 3.6
|
|
||||||
- test: 3.7
|
|
||||||
# - test: 3.8
|
|
||||||
# - test: 3.9
|
# - test: 3.9
|
||||||
# - test: "3.10"
|
# - test: "3.10"
|
||||||
- test: "3.11"
|
- test: "3.11"
|
||||||
|
- test: "3.13"
|
||||||
|
groups:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- stage: Generic_2_17
|
||||||
|
displayName: Generic 2.17
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
nameFormat: Python {0}
|
||||||
|
testFormat: 2.17/generic/{0}
|
||||||
|
targets:
|
||||||
|
- test: "3.7"
|
||||||
|
- test: "3.12"
|
||||||
|
groups:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- stage: Generic_2_16
|
||||||
|
displayName: Generic 2.16
|
||||||
|
dependsOn: []
|
||||||
|
jobs:
|
||||||
|
- template: templates/matrix.yml
|
||||||
|
parameters:
|
||||||
|
nameFormat: Python {0}
|
||||||
|
testFormat: 2.16/generic/{0}
|
||||||
|
targets:
|
||||||
|
- test: "2.7"
|
||||||
|
- test: "3.6"
|
||||||
|
- test: "3.11"
|
||||||
groups:
|
groups:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
@@ -346,50 +334,11 @@ stages:
|
|||||||
nameFormat: Python {0}
|
nameFormat: Python {0}
|
||||||
testFormat: 2.15/generic/{0}
|
testFormat: 2.15/generic/{0}
|
||||||
targets:
|
targets:
|
||||||
|
- test: 3.5
|
||||||
- test: "3.10"
|
- test: "3.10"
|
||||||
groups:
|
groups:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 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: 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
|
|
||||||
- stage: Generic_2_12
|
|
||||||
displayName: Generic 2.12
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
|
||||||
- template: templates/matrix.yml
|
|
||||||
parameters:
|
|
||||||
nameFormat: Python {0}
|
|
||||||
testFormat: 2.12/generic/{0}
|
|
||||||
targets:
|
|
||||||
- test: 2.6
|
|
||||||
- test: 3.9
|
|
||||||
groups:
|
|
||||||
- 1
|
|
||||||
- 2
|
|
||||||
|
|
||||||
## Finally
|
## Finally
|
||||||
|
|
||||||
@@ -397,26 +346,22 @@ stages:
|
|||||||
condition: succeededOrFailed()
|
condition: succeededOrFailed()
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Ansible_devel
|
- Ansible_devel
|
||||||
|
- Ansible_2_17
|
||||||
|
- Ansible_2_16
|
||||||
- Ansible_2_15
|
- Ansible_2_15
|
||||||
- Ansible_2_14
|
|
||||||
- Ansible_2_13
|
|
||||||
- Ansible_2_12
|
|
||||||
- Remote_devel_extra_vms
|
- Remote_devel_extra_vms
|
||||||
- Remote_devel
|
- Remote_devel
|
||||||
|
- Remote_2_17
|
||||||
|
- Remote_2_16
|
||||||
- Remote_2_15
|
- Remote_2_15
|
||||||
- Remote_2_14
|
|
||||||
- Remote_2_13
|
|
||||||
- Remote_2_12
|
|
||||||
- Docker_devel
|
- Docker_devel
|
||||||
|
- Docker_2_17
|
||||||
|
- Docker_2_16
|
||||||
- Docker_2_15
|
- Docker_2_15
|
||||||
- Docker_2_14
|
|
||||||
- Docker_2_13
|
|
||||||
- Docker_2_12
|
|
||||||
- Docker_community_devel
|
- Docker_community_devel
|
||||||
- Generic_devel
|
- Generic_devel
|
||||||
|
- Generic_2_17
|
||||||
|
- Generic_2_16
|
||||||
- Generic_2_15
|
- Generic_2_15
|
||||||
- Generic_2_14
|
|
||||||
- Generic_2_13
|
|
||||||
- Generic_2_12
|
|
||||||
jobs:
|
jobs:
|
||||||
- template: templates/coverage.yml
|
- template: templates/coverage.yml
|
||||||
|
|||||||
119
.github/workflows/ansible-test.yml
vendored
119
.github/workflows/ansible-test.yml
vendored
@@ -32,6 +32,9 @@ jobs:
|
|||||||
- '2.9'
|
- '2.9'
|
||||||
- '2.10'
|
- '2.10'
|
||||||
- '2.11'
|
- '2.11'
|
||||||
|
- '2.12'
|
||||||
|
- '2.13'
|
||||||
|
- '2.14'
|
||||||
# Ansible-test on various stable branches does not yet work well with cgroups v2.
|
# 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
|
# 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
|
# image for these stable branches. The list of branches where this is necessary will
|
||||||
@@ -45,7 +48,9 @@ jobs:
|
|||||||
- name: Perform sanity testing
|
- name: Perform sanity testing
|
||||||
uses: felixfontein/ansible-test-gh-action@main
|
uses: felixfontein/ansible-test-gh-action@main
|
||||||
with:
|
with:
|
||||||
|
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.9", "2.10", "2.11"]'), matrix.ansible) && 'ansible-community/eol-ansible' || 'ansible/ansible' }}
|
||||||
ansible-core-version: stable-${{ matrix.ansible }}
|
ansible-core-version: stable-${{ matrix.ansible }}
|
||||||
|
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||||
pull-request-change-detection: 'true'
|
pull-request-change-detection: 'true'
|
||||||
testing-type: sanity
|
testing-type: sanity
|
||||||
@@ -69,6 +74,9 @@ jobs:
|
|||||||
- '2.9'
|
- '2.9'
|
||||||
- '2.10'
|
- '2.10'
|
||||||
- '2.11'
|
- '2.11'
|
||||||
|
- '2.12'
|
||||||
|
- '2.13'
|
||||||
|
- '2.14'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: >-
|
- name: >-
|
||||||
@@ -76,7 +84,9 @@ jobs:
|
|||||||
Ansible version ${{ matrix.ansible }}
|
Ansible version ${{ matrix.ansible }}
|
||||||
uses: felixfontein/ansible-test-gh-action@main
|
uses: felixfontein/ansible-test-gh-action@main
|
||||||
with:
|
with:
|
||||||
|
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.9", "2.10", "2.11"]'), matrix.ansible) && 'ansible-community/eol-ansible' || 'ansible/ansible' }}
|
||||||
ansible-core-version: stable-${{ matrix.ansible }}
|
ansible-core-version: stable-${{ matrix.ansible }}
|
||||||
|
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||||
pull-request-change-detection: 'true'
|
pull-request-change-detection: 'true'
|
||||||
testing-type: units
|
testing-type: units
|
||||||
@@ -107,14 +117,6 @@ jobs:
|
|||||||
- ansible: ''
|
- ansible: ''
|
||||||
include:
|
include:
|
||||||
# 2.9
|
# 2.9
|
||||||
- ansible: '2.9'
|
|
||||||
docker: fedora31
|
|
||||||
python: ''
|
|
||||||
target: azp/posix/1/
|
|
||||||
- ansible: '2.9'
|
|
||||||
docker: fedora31
|
|
||||||
python: ''
|
|
||||||
target: azp/posix/2/
|
|
||||||
- ansible: '2.9'
|
- ansible: '2.9'
|
||||||
docker: ubuntu1804
|
docker: ubuntu1804
|
||||||
python: ''
|
python: ''
|
||||||
@@ -149,14 +151,6 @@ jobs:
|
|||||||
python: '3.6'
|
python: '3.6'
|
||||||
target: azp/generic/2/
|
target: azp/generic/2/
|
||||||
# 2.11
|
# 2.11
|
||||||
- ansible: '2.11'
|
|
||||||
docker: fedora32
|
|
||||||
python: ''
|
|
||||||
target: azp/posix/1/
|
|
||||||
- ansible: '2.11'
|
|
||||||
docker: fedora32
|
|
||||||
python: ''
|
|
||||||
target: azp/posix/2/
|
|
||||||
- ansible: '2.11'
|
- ansible: '2.11'
|
||||||
docker: alpine3
|
docker: alpine3
|
||||||
python: ''
|
python: ''
|
||||||
@@ -173,6 +167,97 @@ jobs:
|
|||||||
docker: default
|
docker: default
|
||||||
python: '3.8'
|
python: '3.8'
|
||||||
target: azp/generic/2/
|
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/
|
||||||
|
# 2.13
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: opensuse15py2
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/1/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: opensuse15py2
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/2/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: fedora35
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/1/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: fedora35
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/2/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: fedora34
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/1/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: fedora34
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/2/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: ubuntu1804
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/1/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: ubuntu1804
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/2/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: alpine3
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/1/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: alpine3
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/2/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: default
|
||||||
|
python: '3.8'
|
||||||
|
target: azp/generic/1/
|
||||||
|
- ansible: '2.13'
|
||||||
|
docker: default
|
||||||
|
python: '3.8'
|
||||||
|
target: azp/generic/2/
|
||||||
|
# 2.14
|
||||||
|
- ansible: '2.14'
|
||||||
|
docker: ubuntu2004
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/1/
|
||||||
|
- ansible: '2.14'
|
||||||
|
docker: ubuntu2004
|
||||||
|
python: ''
|
||||||
|
target: azp/posix/2/
|
||||||
|
- ansible: '2.14'
|
||||||
|
docker: default
|
||||||
|
python: '3.9'
|
||||||
|
target: azp/generic/1/
|
||||||
|
- ansible: '2.14'
|
||||||
|
docker: default
|
||||||
|
python: '3.9'
|
||||||
|
target: azp/generic/2/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: >-
|
- name: >-
|
||||||
@@ -181,7 +266,9 @@ jobs:
|
|||||||
under Python ${{ matrix.python }}
|
under Python ${{ matrix.python }}
|
||||||
uses: felixfontein/ansible-test-gh-action@main
|
uses: felixfontein/ansible-test-gh-action@main
|
||||||
with:
|
with:
|
||||||
|
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.9", "2.10", "2.11"]'), matrix.ansible) && 'ansible-community/eol-ansible' || 'ansible/ansible' }}
|
||||||
ansible-core-version: stable-${{ matrix.ansible }}
|
ansible-core-version: stable-${{ matrix.ansible }}
|
||||||
|
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||||
docker-image: ${{ matrix.docker }}
|
docker-image: ${{ matrix.docker }}
|
||||||
integration-continue-on-error: 'false'
|
integration-continue-on-error: 'false'
|
||||||
|
|||||||
3
.github/workflows/docs-pr.yml
vendored
3
.github/workflows/docs-pr.yml
vendored
@@ -38,12 +38,15 @@ jobs:
|
|||||||
if: github.repository == 'ansible-collections/community.crypto'
|
if: github.repository == 'ansible-collections/community.crypto'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
needs: [build-docs]
|
needs: [build-docs]
|
||||||
name: Publish Ansible Docs
|
name: Publish Ansible Docs
|
||||||
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
|
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
|
||||||
with:
|
with:
|
||||||
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
|
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
|
||||||
action: ${{ (github.event.action == 'closed' || needs.build-docs.outputs.changed != 'true') && 'teardown' || 'publish' }}
|
action: ${{ (github.event.action == 'closed' || needs.build-docs.outputs.changed != 'true') && 'teardown' || 'publish' }}
|
||||||
|
publish-gh-pages-branch: true
|
||||||
secrets:
|
secrets:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/docs-push.yml
vendored
3
.github/workflows/docs-push.yml
vendored
@@ -43,10 +43,13 @@ jobs:
|
|||||||
if: github.repository == 'ansible-collections/community.crypto'
|
if: github.repository == 'ansible-collections/community.crypto'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
needs: [build-docs]
|
needs: [build-docs]
|
||||||
name: Publish Ansible Docs
|
name: Publish Ansible Docs
|
||||||
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
|
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
|
||||||
with:
|
with:
|
||||||
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
|
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
|
||||||
|
publish-gh-pages-branch: true
|
||||||
secrets:
|
secrets:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
100
.github/workflows/ee.yml
vendored
100
.github/workflows/ee.yml
vendored
@@ -22,25 +22,73 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and test EE (Ⓐ${{ matrix.runner_tag }})
|
name: Build and test EE (${{ matrix.name }})
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
runner_tag:
|
name:
|
||||||
- devel
|
- ''
|
||||||
- stable-2.12-latest
|
ansible_core:
|
||||||
- stable-2.11-latest
|
- ''
|
||||||
- stable-2.9-latest
|
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
|
||||||
|
other_deps: |2
|
||||||
|
python_interpreter:
|
||||||
|
package_system: python3.11 python3.11-pip python3.11-wheel python3.11-cryptography
|
||||||
|
python_path: "/usr/bin/python3.11"
|
||||||
|
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
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
|
path: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.11'
|
||||||
|
|
||||||
- name: Install ansible-builder and ansible-navigator
|
- name: Install ansible-builder and ansible-navigator
|
||||||
run: pip install ansible-builder ansible-navigator
|
run: pip install ansible-builder ansible-navigator
|
||||||
@@ -74,11 +122,26 @@ jobs:
|
|||||||
# EE config
|
# EE config
|
||||||
cat > execution-environment.yml <<EOF
|
cat > execution-environment.yml <<EOF
|
||||||
---
|
---
|
||||||
version: 1
|
version: 3
|
||||||
build_arg_defaults:
|
|
||||||
EE_BASE_IMAGE: 'quay.io/ansible/ansible-runner:${{ matrix.runner_tag }}'
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
ansible_core:
|
||||||
|
package_pip: ${{ matrix.ansible_core }}
|
||||||
|
ansible_runner:
|
||||||
|
package_pip: ${{ matrix.ansible_runner }}
|
||||||
galaxy: requirements.yml
|
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
|
EOF
|
||||||
echo "::group::execution-environment.yml"
|
echo "::group::execution-environment.yml"
|
||||||
cat execution-environment.yml
|
cat execution-environment.yml
|
||||||
@@ -88,26 +151,29 @@ jobs:
|
|||||||
cat > requirements.yml <<EOF
|
cat > requirements.yml <<EOF
|
||||||
---
|
---
|
||||||
collections:
|
collections:
|
||||||
- name: ${COLLECTION_FILENAME}
|
- name: src/${COLLECTION_FILENAME}
|
||||||
type: file
|
type: file
|
||||||
EOF
|
EOF
|
||||||
echo "::group::requirements.yml"
|
echo "::group::requirements.yml"
|
||||||
cat requirements.yml
|
cat requirements.yml
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
|
|
||||||
- name: Build image based on ${{ matrix.runner_tag }}
|
- name: Build image based on ${{ matrix.base_image }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p context/_build/
|
ansible-builder build --verbosity 3 --tag test-ee:latest --container-runtime podman
|
||||||
cp "${{ env.NAMESPACE }}-${{ env.COLLECTION_NAME }}"-*.tar.gz context/_build/
|
|
||||||
ansible-builder build -v 3 -t test-ee:latest --container-runtime=podman
|
- name: Show images
|
||||||
|
run: podman image ls
|
||||||
|
|
||||||
- name: Run basic tests
|
- name: Run basic tests
|
||||||
run: >
|
run: >
|
||||||
ansible-navigator run
|
ansible-navigator run
|
||||||
--mode stdout
|
--mode stdout
|
||||||
|
--container-engine podman
|
||||||
--pull-policy never
|
--pull-policy never
|
||||||
--set-environment-variable ANSIBLE_PRIVATE_ROLE_VARS=true
|
--set-environment-variable ANSIBLE_PRIVATE_ROLE_VARS=true
|
||||||
--execution-environment-image test-ee:latest
|
--execution-environment-image test-ee:latest
|
||||||
-v
|
-v
|
||||||
all.yml
|
all.yml
|
||||||
|
${{ matrix.extra_vars }}
|
||||||
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}/tests/ee
|
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}/tests/ee
|
||||||
|
|||||||
20
.github/workflows/import-galaxy.yml
vendored
Normal file
20
.github/workflows/import-galaxy.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
# 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: import-galaxy
|
||||||
|
'on':
|
||||||
|
# Run CI against all pushes (direct commits, also merged PRs) to main, and all Pull Requests
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- stable-*
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
import-galaxy:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
name: Test to import built collection artifact with Galaxy importer
|
||||||
|
uses: ansible-community/github-action-test-galaxy-import/.github/workflows/test-galaxy-import.yml@main
|
||||||
12
.github/workflows/reuse.yml
vendored
12
.github/workflows/reuse.yml
vendored
@@ -21,14 +21,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Remove some files before checking REUSE compliance
|
||||||
run: |
|
|
||||||
pip install reuse
|
|
||||||
|
|
||||||
- name: Check REUSE compliance (except some PEM files)
|
|
||||||
run: |
|
run: |
|
||||||
rm -f tests/integration/targets/*/files/*.pem
|
rm -f tests/integration/targets/*/files/*.pem
|
||||||
rm -f tests/integration/targets/*/files/roots/*.pem
|
rm -f tests/integration/targets/*/files/roots/*.pem
|
||||||
reuse lint
|
|
||||||
|
- name: REUSE Compliance Check
|
||||||
|
uses: fsfe/reuse-action@v4
|
||||||
|
|||||||
1463
CHANGELOG.md
Normal file
1463
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
3
CHANGELOG.md.license
Normal file
3
CHANGELOG.md.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
|
||||||
366
CHANGELOG.rst
366
CHANGELOG.rst
@@ -4,6 +4,314 @@ Community Crypto Release Notes
|
|||||||
|
|
||||||
.. contents:: Topics
|
.. contents:: Topics
|
||||||
|
|
||||||
|
v2.21.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Feature release.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- certificate_complete_chain - add ability to identify Ed25519 and Ed448 complete chains (https://github.com/ansible-collections/community.crypto/pull/777).
|
||||||
|
- get_certificate - adds ``tls_ctx_options`` option for specifying SSL CTX options (https://github.com/ansible-collections/community.crypto/pull/779).
|
||||||
|
- get_certificate - allow to obtain the certificate chain sent by the server, and the one used for validation, with the new ``get_certificate_chain`` option. Note that this option only works if the module is run with Python 3.10 or newer (https://github.com/ansible-collections/community.crypto/issues/568, https://github.com/ansible-collections/community.crypto/pull/784).
|
||||||
|
|
||||||
|
v2.20.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Feature and bugfix release.
|
||||||
|
|
||||||
|
The deprecations in this release are only relevant for collections that use shared
|
||||||
|
code or docs fragments from this collection.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- acme_certificate - add ``include_renewal_cert_id`` option to allow requesting renewal of a specific certificate according to the current ACME Renewal Information specification draft (https://github.com/ansible-collections/community.crypto/pull/739).
|
||||||
|
|
||||||
|
Deprecated Features
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- acme documentation fragment - the default ``community.crypto.acme[.documentation]`` docs fragment is deprecated and will be removed from community.crypto 3.0.0. Replace it with both the new ``community.crypto.acme.basic`` and ``community.crypto.acme.account`` fragments (https://github.com/ansible-collections/community.crypto/pull/735).
|
||||||
|
- acme.backends module utils - the ``get_cert_information()`` method for a ACME crypto backend must be implemented from community.crypto 3.0.0 on (https://github.com/ansible-collections/community.crypto/pull/736).
|
||||||
|
- crypto.module_backends.common module utils - the ``crypto.module_backends.common`` module utils is deprecated and will be removed from community.crypto 3.0.0. Use the improved ``argspec`` module util instead (https://github.com/ansible-collections/community.crypto/pull/749).
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- x509_crl, x509_certificate, x509_certificate_info - when parsing absolute timestamps which omitted the second count, the first digit of the minutes was used as a one-digit minutes count, and the second digit of the minutes as a one-digit second count (https://github.com/ansible-collections/community.crypto/pull/745).
|
||||||
|
|
||||||
|
New Modules
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- community.crypto.acme_ari_info - Retrieves ACME Renewal Information (ARI) for a certificate.
|
||||||
|
- community.crypto.acme_certificate_deactivate_authz - Deactivate all authz for an ACME v2 order.
|
||||||
|
- community.crypto.acme_certificate_renewal_info - Determine whether a certificate should be renewed or not.
|
||||||
|
|
||||||
|
v2.19.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- crypto.math module utils - change return values for ``quick_is_not_prime()`` and ``convert_int_to_bytes(0, 0)`` for special cases that do not appear when using the collection (https://github.com/ansible-collections/community.crypto/pull/733).
|
||||||
|
- ecs_certificate - fixed ``csr`` option to be empty and allow renewal of a specific certificate according to the Renewal Information specification (https://github.com/ansible-collections/community.crypto/pull/740).
|
||||||
|
- x509_certificate - since community.crypto 2.19.0 the module was no longer idempotent with respect to ``not_before`` and ``not_after`` times. This is now fixed (https://github.com/ansible-collections/community.crypto/issues/753, https://github.com/ansible-collections/community.crypto/pull/754).
|
||||||
|
|
||||||
|
v2.19.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix and feature release.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- When using cryptography >= 42.0.0, use offset-aware ``datetime.datetime`` objects (with timezone UTC) instead of offset-naive UTC timestamps (https://github.com/ansible-collections/community.crypto/issues/726, https://github.com/ansible-collections/community.crypto/pull/727).
|
||||||
|
- openssh_cert - avoid UTC functions deprecated in Python 3.12 when using Python 3 (https://github.com/ansible-collections/community.crypto/pull/727).
|
||||||
|
|
||||||
|
Deprecated Features
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- acme.backends module utils - from community.crypto on, all implementations of ``CryptoBackend`` must override ``get_ordered_csr_identifiers()``. The current default implementation, which simply sorts the result of ``get_csr_identifiers()``, will then be removed (https://github.com/ansible-collections/community.crypto/pull/725).
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- acme_certificate - respect the order of the CNAME and SAN identifiers that are passed on when creating an ACME order (https://github.com/ansible-collections/community.crypto/issues/723, https://github.com/ansible-collections/community.crypto/pull/725).
|
||||||
|
|
||||||
|
New Modules
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- community.crypto.x509_certificate_convert - Convert X.509 certificates
|
||||||
|
|
||||||
|
v2.18.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix and feature release.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- x509_crl - the new option ``serial_numbers`` allow to configure in which format serial numbers can be provided to ``revoked_certificates[].serial_number``. The default is as integers (``serial_numbers=integer``) for backwards compatibility; setting ``serial_numbers=hex-octets`` allows to specify colon-separated hex octet strings like ``00:11:22:FF`` (https://github.com/ansible-collections/community.crypto/issues/687, https://github.com/ansible-collections/community.crypto/pull/715).
|
||||||
|
|
||||||
|
Deprecated Features
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- openssl_csr_pipe, openssl_privatekey_pipe, x509_certificate_pipe - the current behavior of check mode is deprecated and will change in community.crypto 3.0.0. The current behavior is similar to the modules without ``_pipe``: if the object needs to be (re-)generated, only the ``changed`` status is set, but the object is not updated. From community.crypto 3.0.0 on, the modules will ignore check mode and always act as if check mode is not active. This behavior can already achieved now by adding ``check_mode: false`` to the task. If you think this breaks your use-case of this module, please `create an issue in the community.crypto repository <https://github.com/ansible-collections/community.crypto/issues/new/choose>`__ (https://github.com/ansible-collections/community.crypto/issues/712, https://github.com/ansible-collections/community.crypto/pull/714).
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- luks_device - fixed module a bug that prevented using ``remove_keyslot`` with the value ``0`` (https://github.com/ansible-collections/community.crypto/pull/710).
|
||||||
|
- luks_device - fixed module falsely outputting ``changed=false`` when trying to add a new slot with a key that is already present in another slot. The module now rejects adding keys that are already present in another slot (https://github.com/ansible-collections/community.crypto/pull/710).
|
||||||
|
- luks_device - fixed testing of LUKS passphrases in when specifying a keyslot for cryptsetup version 2.0.3. The output of this cryptsetup version slightly differs from later versions (https://github.com/ansible-collections/community.crypto/pull/710).
|
||||||
|
|
||||||
|
New Plugins
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Filter
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
- community.crypto.parse_serial - Convert a serial number as a colon-separated list of hex numbers to an integer
|
||||||
|
- community.crypto.to_serial - Convert an integer to a colon-separated list of hex numbers
|
||||||
|
|
||||||
|
v2.17.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release for compatibility with cryptography 42.0.0.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- openssl_dhparam - was using an internal function instead of the public API to load DH param files when using the ``cryptography`` backend. The internal function was removed in cryptography 42.0.0. The module now uses the public API, which has been available since support for DH params was added to cryptography (https://github.com/ansible-collections/community.crypto/pull/698).
|
||||||
|
- openssl_privatekey_info - ``check_consistency=true`` no longer works for RSA keys with cryptography 42.0.0+ (https://github.com/ansible-collections/community.crypto/pull/701).
|
||||||
|
- openssl_privatekey_info - ``check_consistency=true`` now reports a warning if it cannot determine consistency (https://github.com/ansible-collections/community.crypto/pull/705).
|
||||||
|
|
||||||
|
v2.17.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Feature release.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- luks_device - add allow discards option (https://github.com/ansible-collections/community.crypto/pull/693).
|
||||||
|
|
||||||
|
v2.16.2
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- acme_* modules - directly react on bad return data for account creation/retrieval/updating requests (https://github.com/ansible-collections/community.crypto/pull/682).
|
||||||
|
- acme_* modules - fix improved error reporting in case of socket errors, bad status lines, and unknown connection errors (https://github.com/ansible-collections/community.crypto/pull/684).
|
||||||
|
- acme_* modules - increase number of retries from 5 to 10 to increase stability with unstable ACME endpoints (https://github.com/ansible-collections/community.crypto/pull/685).
|
||||||
|
- acme_* modules - make account registration handling more flexible to accept 404 instead of 400 send by DigiCert's ACME endpoint when an account does not exist (https://github.com/ansible-collections/community.crypto/pull/681).
|
||||||
|
|
||||||
|
v2.16.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- acme_* modules - also retry requests in case of socket errors, bad status lines, and unknown connection errors; improve error messages in these cases (https://github.com/ansible-collections/community.crypto/issues/680).
|
||||||
|
|
||||||
|
v2.16.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- luks_devices - add new options ``keyslot``, ``new_keyslot``, and ``remove_keyslot`` to allow adding/removing keys to/from specific keyslots (https://github.com/ansible-collections/community.crypto/pull/664).
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- openssl_pkcs12 - modify autodetect to not detect pyOpenSSL >= 23.3.0, which removed PKCS#12 support (https://github.com/ansible-collections/community.crypto/pull/666).
|
||||||
|
|
||||||
|
v2.15.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- acme_* modules - correctly handle error documents without ``type`` (https://github.com/ansible-collections/community.crypto/issues/651, https://github.com/ansible-collections/community.crypto/pull/652).
|
||||||
|
|
||||||
|
v2.15.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix and feature release.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- openssh_keypair - fail when comment cannot be updated (https://github.com/ansible-collections/community.crypto/pull/646).
|
||||||
|
|
||||||
|
Deprecated Features
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- get_certificate - the default ``false`` of the ``asn1_base64`` option is deprecated and will change to ``true`` in community.crypto 3.0.0 (https://github.com/ansible-collections/community.crypto/pull/600).
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- openssh_cert, openssh_keypair - the modules ignored return codes of ``ssh`` and ``ssh-keygen`` in some cases (https://github.com/ansible-collections/community.crypto/issues/645, https://github.com/ansible-collections/community.crypto/pull/646).
|
||||||
|
- openssh_keypair - fix comment updating for OpenSSH before 6.5 (https://github.com/ansible-collections/community.crypto/pull/646).
|
||||||
|
|
||||||
|
New Plugins
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Filter
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
- community.crypto.gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key
|
||||||
|
|
||||||
|
Lookup
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
- community.crypto.gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key file
|
||||||
|
|
||||||
|
v2.14.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix and maintenance release with updated documentation.
|
||||||
|
|
||||||
|
From this version on, community.crypto is using the new `Ansible semantic markup
|
||||||
|
<https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#semantic-markup-within-module-documentation>`__
|
||||||
|
in its documentation. If you look at documentation with the ansible-doc CLI tool
|
||||||
|
from ansible-core before 2.15, please note that it does not render the markup
|
||||||
|
correctly. You should be still able to read it in most cases, but you need
|
||||||
|
ansible-core 2.15 or later to see it as it is intended. Alternatively you can
|
||||||
|
look at `the devel docsite <https://docs.ansible.com/ansible/devel/collections/community/crypto/>`__
|
||||||
|
for the rendered HTML version of the documentation of the latest release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Fix PEM detection/identification to also accept random other lines before the line starting with ``-----BEGIN`` (https://github.com/ansible-collections/community.crypto/issues/627, https://github.com/ansible-collections/community.crypto/pull/628).
|
||||||
|
|
||||||
|
Known Issues
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Ansible markup will show up in raw form on ansible-doc text output for ansible-core before 2.15. If you have trouble deciphering the documentation markup, please upgrade to ansible-core 2.15 (or newer), or read the HTML documentation on https://docs.ansible.com/ansible/devel/collections/community/crypto/.
|
||||||
|
|
||||||
|
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
|
||||||
|
---------------
|
||||||
|
|
||||||
|
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
|
v2.13.0
|
||||||
=======
|
=======
|
||||||
@@ -90,12 +398,12 @@ New Plugins
|
|||||||
Filter
|
Filter
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
- openssl_csr_info - Retrieve information from OpenSSL Certificate Signing Requests (CSR)
|
- community.crypto.openssl_csr_info - Retrieve information from OpenSSL Certificate Signing Requests (CSR)
|
||||||
- openssl_privatekey_info - Retrieve information from OpenSSL private keys
|
- community.crypto.openssl_privatekey_info - Retrieve information from OpenSSL private keys
|
||||||
- openssl_publickey_info - Retrieve information from OpenSSL public keys in PEM format
|
- community.crypto.openssl_publickey_info - Retrieve information from OpenSSL public keys in PEM format
|
||||||
- split_pem - Split PEM file contents into multiple objects
|
- community.crypto.split_pem - Split PEM file contents into multiple objects
|
||||||
- x509_certificate_info - Retrieve information from X.509 certificates in PEM format
|
- community.crypto.x509_certificate_info - Retrieve information from X.509 certificates in PEM format
|
||||||
- x509_crl_info - Retrieve information from X.509 CRLs in PEM format
|
- community.crypto.x509_crl_info - Retrieve information from X.509 CRLs in PEM format
|
||||||
|
|
||||||
v2.9.0
|
v2.9.0
|
||||||
======
|
======
|
||||||
@@ -225,7 +533,6 @@ 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
|
version number has been bumped to 2.3.4 and this changelog entry for 2.3.4
|
||||||
has been added.
|
has been added.
|
||||||
|
|
||||||
|
|
||||||
v2.3.3
|
v2.3.3
|
||||||
======
|
======
|
||||||
|
|
||||||
@@ -280,7 +587,7 @@ 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).
|
- 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).
|
- Support automatic conversion for Internalionalized Domain Names (IDNs). When passing general names, for example Subject Alternative 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).
|
- 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 ``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).
|
- luks_devices - added ``persistent`` option when opening LUKS2 containers (https://github.com/ansible-collections/community.crypto/pull/434).
|
||||||
@@ -332,7 +639,6 @@ 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.
|
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
|
Bugfixes
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -396,8 +702,8 @@ Bugfixes
|
|||||||
New Modules
|
New Modules
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- crypto_info - Retrieve cryptographic capabilities
|
- community.crypto.crypto_info - Retrieve cryptographic capabilities
|
||||||
- openssl_privatekey_convert - Convert OpenSSL private keys
|
- community.crypto.openssl_privatekey_convert - Convert OpenSSL private keys
|
||||||
|
|
||||||
v2.0.2
|
v2.0.2
|
||||||
======
|
======
|
||||||
@@ -436,7 +742,6 @@ 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.
|
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
|
Minor Changes
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@@ -619,20 +924,20 @@ Minor Changes
|
|||||||
- openssh_keypair - added ``passphrase`` parameter for encrypting/decrypting OpenSSH private keys (https://github.com/ansible-collections/community.crypto/pull/225).
|
- openssh_keypair - added ``passphrase`` parameter for encrypting/decrypting OpenSSH private keys (https://github.com/ansible-collections/community.crypto/pull/225).
|
||||||
- openssl_csr - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- openssl_csr - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- openssl_csr_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233).
|
- openssl_csr_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233).
|
||||||
- openssl_csr_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/204).
|
- openssl_csr_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/204).
|
||||||
- openssl_csr_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- openssl_csr_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- openssl_pkcs12 - added option ``select_crypto_backend`` and a ``cryptography`` backend. This requires cryptography 3.0 or newer, and does not support the ``iter_size`` and ``maciter_size`` options (https://github.com/ansible-collections/community.crypto/pull/234).
|
- openssl_pkcs12 - added option ``select_crypto_backend`` and a ``cryptography`` backend. This requires cryptography 3.0 or newer, and does not support the ``iter_size`` and ``maciter_size`` options (https://github.com/ansible-collections/community.crypto/pull/234).
|
||||||
- openssl_privatekey - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- openssl_privatekey - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- openssl_privatekey_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/205).
|
- openssl_privatekey_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/205).
|
||||||
- openssl_privatekey_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- openssl_privatekey_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- openssl_publickey - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- openssl_publickey - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- x509_certificate - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- x509_certificate - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- x509_certificate_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233).
|
- x509_certificate_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233).
|
||||||
- x509_certificate_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/206).
|
- x509_certificate_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/206).
|
||||||
- x509_certificate_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- x509_certificate_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- x509_crl - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
- x509_crl - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
|
||||||
- x509_crl_info - add ``list_revoked_certificates`` option to avoid enumerating all revoked certificates (https://github.com/ansible-collections/community.crypto/pull/232).
|
- x509_crl_info - add ``list_revoked_certificates`` option to avoid enumerating all revoked certificates (https://github.com/ansible-collections/community.crypto/pull/232).
|
||||||
- x509_crl_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/203).
|
- x509_crl_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/203).
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
--------
|
--------
|
||||||
@@ -644,7 +949,7 @@ Bugfixes
|
|||||||
New Modules
|
New Modules
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- openssl_publickey_info - Provide information for OpenSSL public keys
|
- community.crypto.openssl_publickey_info - Provide information for OpenSSL public keys
|
||||||
|
|
||||||
v1.6.2
|
v1.6.2
|
||||||
======
|
======
|
||||||
@@ -755,16 +1060,15 @@ Release Summary
|
|||||||
|
|
||||||
Contains new modules ``openssl_privatekey_pipe``, ``openssl_csr_pipe`` and ``x509_certificate_pipe`` which allow to create or update private keys, CSRs and X.509 certificates without having to write them to disk.
|
Contains new modules ``openssl_privatekey_pipe``, ``openssl_csr_pipe`` and ``x509_certificate_pipe`` which allow to create or update private keys, CSRs and X.509 certificates without having to write them to disk.
|
||||||
|
|
||||||
|
|
||||||
Minor Changes
|
Minor Changes
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
- openssh_cert - add module parameter ``use_agent`` to enable using signing keys stored in ssh-agent (https://github.com/ansible-collections/community.crypto/issues/116).
|
- openssh_cert - add module parameter ``use_agent`` to enable using signing keys stored in ssh-agent (https://github.com/ansible-collections/community.crypto/issues/116).
|
||||||
- openssl_csr - refactor module to allow code re-use by openssl_csr_pipe (https://github.com/ansible-collections/community.crypto/pull/123).
|
- openssl_csr - refactor module to allow code reuse by openssl_csr_pipe (https://github.com/ansible-collections/community.crypto/pull/123).
|
||||||
- openssl_privatekey - refactor module to allow code re-use by openssl_privatekey_pipe (https://github.com/ansible-collections/community.crypto/pull/119).
|
- openssl_privatekey - refactor module to allow code reuse by openssl_privatekey_pipe (https://github.com/ansible-collections/community.crypto/pull/119).
|
||||||
- openssl_privatekey - the elliptic curve ``secp192r1`` now triggers a security warning. Elliptic curves of at least 224 bits should be used for new keys; see `here <https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#elliptic-curves>`_ (https://github.com/ansible-collections/community.crypto/pull/132).
|
- openssl_privatekey - the elliptic curve ``secp192r1`` now triggers a security warning. Elliptic curves of at least 224 bits should be used for new keys; see `here <https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#elliptic-curves>`_ (https://github.com/ansible-collections/community.crypto/pull/132).
|
||||||
- x509_certificate - for the ``selfsigned`` provider, a CSR is not required anymore. If no CSR is provided, the module behaves as if a minimal CSR which only contains the public key has been provided (https://github.com/ansible-collections/community.crypto/issues/32, https://github.com/ansible-collections/community.crypto/pull/129).
|
- x509_certificate - for the ``selfsigned`` provider, a CSR is not required anymore. If no CSR is provided, the module behaves as if a minimal CSR which only contains the public key has been provided (https://github.com/ansible-collections/community.crypto/issues/32, https://github.com/ansible-collections/community.crypto/pull/129).
|
||||||
- x509_certificate - refactor module to allow code re-use by x509_certificate_pipe (https://github.com/ansible-collections/community.crypto/pull/135).
|
- x509_certificate - refactor module to allow code reuse by x509_certificate_pipe (https://github.com/ansible-collections/community.crypto/pull/135).
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
--------
|
--------
|
||||||
@@ -776,9 +1080,9 @@ Bugfixes
|
|||||||
New Modules
|
New Modules
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- openssl_csr_pipe - Generate OpenSSL Certificate Signing Request (CSR)
|
- community.crypto.openssl_csr_pipe - Generate OpenSSL Certificate Signing Request (CSR)
|
||||||
- openssl_privatekey_pipe - Generate OpenSSL private keys without disk access
|
- community.crypto.openssl_privatekey_pipe - Generate OpenSSL private keys without disk access
|
||||||
- x509_certificate_pipe - Generate and/or check OpenSSL certificates
|
- community.crypto.x509_certificate_pipe - Generate and/or check OpenSSL certificates
|
||||||
|
|
||||||
v1.2.0
|
v1.2.0
|
||||||
======
|
======
|
||||||
@@ -831,7 +1135,6 @@ Release Summary
|
|||||||
|
|
||||||
Release for Ansible 2.10.0.
|
Release for Ansible 2.10.0.
|
||||||
|
|
||||||
|
|
||||||
Minor Changes
|
Minor Changes
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@@ -855,8 +1158,8 @@ Bugfixes
|
|||||||
New Modules
|
New Modules
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- openssl_signature - Sign data with openssl
|
- community.crypto.openssl_signature - Sign data with openssl
|
||||||
- openssl_signature_info - Verify signatures with openssl
|
- community.crypto.openssl_signature_info - Verify signatures with openssl
|
||||||
|
|
||||||
v1.0.0
|
v1.0.0
|
||||||
======
|
======
|
||||||
@@ -866,7 +1169,6 @@ Release Summary
|
|||||||
|
|
||||||
This is the first proper release of the ``community.crypto`` collection. This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0.
|
This is the first proper release of the ``community.crypto`` collection. This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0.
|
||||||
|
|
||||||
|
|
||||||
Minor Changes
|
Minor Changes
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@@ -877,7 +1179,7 @@ Minor Changes
|
|||||||
- openssh_keypair - instead of regenerating some broken or password protected keys, fail the module. Keys can still be regenerated by calling the module with ``force=yes``.
|
- openssh_keypair - instead of regenerating some broken or password protected keys, fail the module. Keys can still be regenerated by calling the module with ``force=yes``.
|
||||||
- openssh_keypair - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys.
|
- openssh_keypair - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys.
|
||||||
- openssl_* modules - the cryptography backend now properly supports ``dirName``, ``otherName`` and ``RID`` (Registered ID) names.
|
- openssl_* modules - the cryptography backend now properly supports ``dirName``, ``otherName`` and ``RID`` (Registered ID) names.
|
||||||
- openssl_certificate - Add option for changing which ACME directory to use with acme-tiny. Set the default ACME directory to Let's Encrypt instead of using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time being, so no action should be neccessary.)
|
- openssl_certificate - Add option for changing which ACME directory to use with acme-tiny. Set the default ACME directory to Let's Encrypt instead of using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time being, so no action should be necessary.)
|
||||||
- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0
|
- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0
|
||||||
- openssl_certificate - allow to provide content of some input files via the ``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and ``ownca_content`` options.
|
- openssl_certificate - allow to provide content of some input files via the ``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and ``ownca_content`` options.
|
||||||
- openssl_certificate - allow to return the existing/generated certificate directly as ``certificate`` by setting ``return_content`` to ``yes``.
|
- openssl_certificate - allow to return the existing/generated certificate directly as ``certificate`` by setting ``return_content`` to ``yes``.
|
||||||
@@ -932,6 +1234,6 @@ Bugfixes
|
|||||||
New Modules
|
New Modules
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- ecs_domain - Request validation of a domain with the Entrust Certificate Services (ECS) API
|
- community.crypto.ecs_domain - Request validation of a domain with the Entrust Certificate Services (ECS) API
|
||||||
- x509_crl - Generate Certificate Revocation Lists (CRLs)
|
- community.crypto.x509_crl - Generate Certificate Revocation Lists (CRLs)
|
||||||
- x509_crl_info - Retrieve information on Certificate Revocation Lists (CRLs)
|
- community.crypto.x509_crl_info - Retrieve information on Certificate Revocation Lists (CRLs)
|
||||||
|
|||||||
83
README.md
83
README.md
@@ -18,7 +18,7 @@ Please note that this collection does **not** support Windows targets.
|
|||||||
|
|
||||||
## Tested with Ansible
|
## Tested with Ansible
|
||||||
|
|
||||||
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.
|
Tested with the current Ansible 2.9, ansible-base 2.10, ansible-core 2.11, ansible-core 2.12, ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, and ansible-core-2.17 releases and the current development version of ansible-core. Ansible versions before 2.9.10 are not supported.
|
||||||
|
|
||||||
## External requirements
|
## External requirements
|
||||||
|
|
||||||
@@ -38,39 +38,52 @@ If you use the Ansible package and do not update collections independently, use
|
|||||||
|
|
||||||
## Included content
|
## Included content
|
||||||
|
|
||||||
- OpenSSL / PKI modules:
|
- OpenSSL / PKI modules and plugins:
|
||||||
- openssl_csr_info
|
- certificate_complete_chain module
|
||||||
- openssl_csr
|
- openssl_csr_info module and filter
|
||||||
- openssl_dhparam
|
- openssl_csr_pipe module
|
||||||
- openssl_pkcs12
|
- openssl_csr module
|
||||||
- openssl_privatekey_info
|
- openssl_dhparam module
|
||||||
- openssl_privatekey
|
- openssl_pkcs12 module
|
||||||
- openssl_publickey
|
- openssl_privatekey_convert module
|
||||||
- openssl_signature_info
|
- openssl_privatekey_info module and filter
|
||||||
- openssl_signature
|
- openssl_privatekey_pipe module
|
||||||
- x509_certificate_info
|
- openssl_privatekey module
|
||||||
- x509_certificate
|
- openssl_publickey_info module and filter
|
||||||
- x509_crl_info
|
- openssl_publickey module
|
||||||
- x509_crl
|
- openssl_signature_info module
|
||||||
- certificate_complete_chain
|
- openssl_signature module
|
||||||
- OpenSSH modules:
|
- split_pem filter
|
||||||
- openssh_cert
|
- x509_certificate_convert module
|
||||||
- openssh_keypair
|
- x509_certificate_info module and filter
|
||||||
- ACME modules:
|
- x509_certificate_pipe module
|
||||||
- acme_account_info
|
- x509_certificate module
|
||||||
- acme_account
|
- x509_crl_info module and filter
|
||||||
- acme_certificate
|
- x509_crl module
|
||||||
- acme_certificate_revoke
|
- OpenSSH modules and plugins:
|
||||||
- acme_challenge_cert_helper
|
- openssh_cert module
|
||||||
- acme_inspect
|
- openssh_keypair module
|
||||||
- ECS modules:
|
- ACME modules and plugins:
|
||||||
- ecs_certificate
|
- acme_account_info module
|
||||||
- ecs_domain
|
- acme_account module
|
||||||
- Miscellaneous modules:
|
- acme_ari_info module
|
||||||
- get_certificate
|
- acme_certificate module
|
||||||
- luks_device
|
- acme_certificate_deactivate_authz module
|
||||||
|
- acme_certificate_revoke module
|
||||||
|
- acme_challenge_cert_helper module
|
||||||
|
- acme_inspect module
|
||||||
|
- ECS modules and plugins:
|
||||||
|
- ecs_certificate module
|
||||||
|
- ecs_domain module
|
||||||
|
- GnuPG modules and plugins:
|
||||||
|
- gpg_fingerprint lookup and filter
|
||||||
|
- Miscellaneous modules and plugins:
|
||||||
|
- crypto_info module
|
||||||
|
- get_certificate module
|
||||||
|
- luks_device module
|
||||||
|
- parse_serial and to_serial filters
|
||||||
|
|
||||||
You can also find a list of all modules with documentation on the [Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/crypto/).
|
You can also find a list of all modules and plugins with documentation on the [Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/crypto/), or the [latest commit collection documentation](https://ansible-collections.github.io/community.crypto/branch/main/).
|
||||||
|
|
||||||
## Using this collection
|
## Using this collection
|
||||||
|
|
||||||
@@ -102,7 +115,7 @@ See [Ansible's dev guide](https://docs.ansible.com/ansible/devel/dev_guide/devel
|
|||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
|
||||||
See the [changelog](https://github.com/ansible-collections/community.crypto/blob/main/CHANGELOG.rst).
|
See the [changelog](https://github.com/ansible-collections/community.crypto/blob/main/CHANGELOG.md).
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
@@ -129,6 +142,6 @@ This collection is primarily licensed and distributed as a whole under the GNU G
|
|||||||
|
|
||||||
See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/community.crypto/blob/main/COPYING) for 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.
|
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`, `tests/integration/targets/prepare_jinja2_compat/filter_plugins/jinja_compatibility.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.
|
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.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,23 +11,31 @@ keep_fragments: false
|
|||||||
mention_ancestor: true
|
mention_ancestor: true
|
||||||
new_plugins_after_name: removed_features
|
new_plugins_after_name: removed_features
|
||||||
notesdir: fragments
|
notesdir: fragments
|
||||||
|
output_formats:
|
||||||
|
- md
|
||||||
|
- rst
|
||||||
prelude_section_name: release_summary
|
prelude_section_name: release_summary
|
||||||
prelude_section_title: Release Summary
|
prelude_section_title: Release Summary
|
||||||
sections:
|
sections:
|
||||||
- - major_changes
|
- - major_changes
|
||||||
- Major Changes
|
- Major Changes
|
||||||
- - minor_changes
|
- - minor_changes
|
||||||
- Minor Changes
|
- Minor Changes
|
||||||
- - breaking_changes
|
- - breaking_changes
|
||||||
- Breaking Changes / Porting Guide
|
- Breaking Changes / Porting Guide
|
||||||
- - deprecated_features
|
- - deprecated_features
|
||||||
- Deprecated Features
|
- Deprecated Features
|
||||||
- - removed_features
|
- - removed_features
|
||||||
- Removed Features (previously deprecated)
|
- Removed Features (previously deprecated)
|
||||||
- - security_fixes
|
- - security_fixes
|
||||||
- Security Fixes
|
- Security Fixes
|
||||||
- - bugfixes
|
- - bugfixes
|
||||||
- Bugfixes
|
- Bugfixes
|
||||||
- - known_issues
|
- - known_issues
|
||||||
- Known Issues
|
- Known Issues
|
||||||
title: Community Crypto
|
title: Community Crypto
|
||||||
|
trivial_section_name: trivial
|
||||||
|
use_fqcn: true
|
||||||
|
add_plugin_period: true
|
||||||
|
changelog_nice_yaml: true
|
||||||
|
changelog_sort: version
|
||||||
|
|||||||
7
docs/docsite/config.yml
Normal file
7
docs/docsite/config.yml
Normal file
@@ -0,0 +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
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
write_changelog: true
|
||||||
@@ -25,3 +25,7 @@ communication:
|
|||||||
mailing_lists:
|
mailing_lists:
|
||||||
- topic: Ansible Project List
|
- topic: Ansible Project List
|
||||||
url: https://groups.google.com/g/ansible-project
|
url: https://groups.google.com/g/ansible-project
|
||||||
|
forums:
|
||||||
|
- topic: Ansible Forum
|
||||||
|
# The following URL directly points to the "Get Help" section
|
||||||
|
url: https://forum.ansible.com/c/help/6/none
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
How to create a small CA
|
How to create a small CA
|
||||||
========================
|
========================
|
||||||
|
|
||||||
The `community.crypto collection <https://galaxy.ansible.com/community/crypto>`_ offers multiple modules that create private keys, certificate signing requests, and certificates. This guide shows how to create your own small CA and how to use it to sign certificates.
|
The `community.crypto collection <https://galaxy.ansible.com/ui/repo/published/community/crypto/>`_ offers multiple modules that create private keys, certificate signing requests, and certificates. This guide shows how to create your own small CA and how to use it to sign certificates.
|
||||||
|
|
||||||
In all examples, we assume that the CA's private key is password protected, where the password is provided in the ``secret_ca_passphrase`` variable.
|
In all examples, we assume that the CA's private key is password protected, where the password is provided in the ``secret_ca_passphrase`` variable.
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
How to create self-signed certificates
|
How to create self-signed certificates
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
The `community.crypto collection <https://galaxy.ansible.com/community/crypto>`_ offers multiple modules that create private keys, certificate signing requests, and certificates. This guide shows how to create self-signed certificates.
|
The `community.crypto collection <https://galaxy.ansible.com/ui/repo/published/community/crypto/>`_ offers multiple modules that create private keys, certificate signing requests, and certificates. This guide shows how to create self-signed certificates.
|
||||||
|
|
||||||
For creating any kind of certificate, you always have to start with a private key. You can use the :ref:`community.crypto.openssl_privatekey module <ansible_collections.community.crypto.openssl_privatekey_module>` to create a private key. If you only specify ``path``, the default parameters will be used. This will result in a 4096 bit RSA private key:
|
For creating any kind of certificate, you always have to start with a private key. You can use the :ref:`community.crypto.openssl_privatekey module <ansible_collections.community.crypto.openssl_privatekey_module>` to create a private key. If you only specify :ansopt:`community.crypto.openssl_privatekey#module:path`, the default parameters will be used. This will result in a 4096 bit RSA private key:
|
||||||
|
|
||||||
.. code-block:: yaml+jinja
|
.. code-block:: yaml+jinja
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ For creating any kind of certificate, you always have to start with a private ke
|
|||||||
community.crypto.openssl_privatekey:
|
community.crypto.openssl_privatekey:
|
||||||
path: /path/to/certificate.key
|
path: /path/to/certificate.key
|
||||||
|
|
||||||
You can specify ``type`` to select another key type, ``size`` to select a different key size (only available for RSA and DSA keys), or ``passphrase`` if you want to store the key password-protected:
|
You can specify :ansopt:`community.crypto.openssl_privatekey#module:type` to select another key type, :ansopt:`community.crypto.openssl_privatekey#module:size` to select a different key size (only available for RSA and DSA keys), or :ansopt:`community.crypto.openssl_privatekey#module:passphrase` if you want to store the key password-protected:
|
||||||
|
|
||||||
.. code-block:: yaml+jinja
|
.. code-block:: yaml+jinja
|
||||||
|
|
||||||
@@ -38,9 +38,9 @@ To create a very simple self-signed certificate with no specific information, yo
|
|||||||
privatekey_path: /path/to/certificate.key
|
privatekey_path: /path/to/certificate.key
|
||||||
provider: selfsigned
|
provider: selfsigned
|
||||||
|
|
||||||
(If you used ``passphrase`` for the private key, you have to provide ``privatekey_passphrase``.)
|
(If you used :ansopt:`community.crypto.openssl_privatekey#module:passphrase` for the private key, you have to provide :ansopt:`community.crypto.x509_certificate#module:privatekey_passphrase`.)
|
||||||
|
|
||||||
You can use ``selfsigned_not_after`` to define when the certificate expires (default: in roughly 10 years), and ``selfsigned_not_before`` to define from when the certificate is valid (default: now).
|
You can use :ansopt:`community.crypto.x509_certificate#module:selfsigned_not_after` to define when the certificate expires (default: in roughly 10 years), and :ansopt:`community.crypto.x509_certificate#module:selfsigned_not_before` to define from when the certificate is valid (default: now).
|
||||||
|
|
||||||
To define further properties of the certificate, like the subject, Subject Alternative Names (SANs), key usages, name constraints, etc., you need to first create a Certificate Signing Request (CSR) and provide it to the :ref:`community.crypto.x509_certificate module <ansible_collections.community.crypto.x509_certificate_module>`. If you do not need the CSR file, you can use the :ref:`community.crypto.openssl_csr_pipe module <ansible_collections.community.crypto.openssl_csr_pipe_module>` as in the example below. (To store it to disk, use the :ref:`community.crypto.openssl_csr module <ansible_collections.community.crypto.openssl_csr_module>` instead.)
|
To define further properties of the certificate, like the subject, Subject Alternative Names (SANs), key usages, name constraints, etc., you need to first create a Certificate Signing Request (CSR) and provide it to the :ref:`community.crypto.x509_certificate module <ansible_collections.community.crypto.x509_certificate_module>`. If you do not need the CSR file, you can use the :ref:`community.crypto.openssl_csr_pipe module <ansible_collections.community.crypto.openssl_csr_pipe_module>` as in the example below. (To store it to disk, use the :ref:`community.crypto.openssl_csr module <ansible_collections.community.crypto.openssl_csr_module>` instead.)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace: community
|
namespace: community
|
||||||
name: crypto
|
name: crypto
|
||||||
version: 2.13.0
|
version: 2.21.0
|
||||||
readme: README.md
|
readme: README.md
|
||||||
authors:
|
authors:
|
||||||
- Ansible (github.com/ansible)
|
- Ansible (github.com/ansible)
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ openssl [platform:rpm]
|
|||||||
python3-cryptography [platform:dpkg]
|
python3-cryptography [platform:dpkg]
|
||||||
python3-cryptography [platform:rpm]
|
python3-cryptography [platform:rpm]
|
||||||
python3-openssl [platform:dpkg]
|
python3-openssl [platform:dpkg]
|
||||||
# On RHEL 9+ and CentOS Stream 9+, python3-pyOpenSSL is part of EPEL
|
# On RHEL 9+, CentOS Stream 9+, and Rocky Linux 9+, python3-pyOpenSSL is part of EPEL
|
||||||
python3-pyOpenSSL [platform:rpm !platform:rhel !platform:centos]
|
python3-pyOpenSSL [platform:rpm !platform:rhel !platform:centos !platform:rocky]
|
||||||
python3-pyOpenSSL [platform:rhel-6 platform:centos-6 platform:rhel-7 platform:centos-7 platform:rhel-8 platform:centos-8]
|
python3-pyOpenSSL [platform:rhel-8]
|
||||||
python3-pyOpenSSL [platform:rhel platform:centos !platform:rhel-6 !platform:centos-6 !platform:rhel-7 !platform:centos-7 !platform:rhel-8 !platform:centos-8 epel]
|
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]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ requires_ansible: '>=2.9.10'
|
|||||||
action_groups:
|
action_groups:
|
||||||
acme:
|
acme:
|
||||||
- acme_inspect
|
- acme_inspect
|
||||||
|
- acme_certificate_deactivate_authz
|
||||||
- acme_certificate_revoke
|
- acme_certificate_revoke
|
||||||
- acme_certificate
|
- acme_certificate
|
||||||
- acme_account
|
- acme_account
|
||||||
|
|||||||
@@ -51,6 +51,16 @@ class PrivateKeyModule(object):
|
|||||||
self.module_backend.generate_private_key()
|
self.module_backend.generate_private_key()
|
||||||
privatekey_data = self.module_backend.get_private_key_data()
|
privatekey_data = self.module_backend.get_private_key_data()
|
||||||
self.privatekey_bytes = privatekey_data
|
self.privatekey_bytes = privatekey_data
|
||||||
|
else:
|
||||||
|
self.module.deprecate(
|
||||||
|
'Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0'
|
||||||
|
' to behave the same as without check mode. You can get that behavior right now'
|
||||||
|
' by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this'
|
||||||
|
' breaks your use-case of this module, please create an issue in the'
|
||||||
|
' community.crypto repository',
|
||||||
|
version='3.0.0',
|
||||||
|
collection_name='community.crypto',
|
||||||
|
)
|
||||||
self.changed = True
|
self.changed = True
|
||||||
elif self.module_backend.needs_conversion():
|
elif self.module_backend.needs_conversion():
|
||||||
# Convert
|
# Convert
|
||||||
@@ -58,6 +68,16 @@ class PrivateKeyModule(object):
|
|||||||
self.module_backend.convert_private_key()
|
self.module_backend.convert_private_key()
|
||||||
privatekey_data = self.module_backend.get_private_key_data()
|
privatekey_data = self.module_backend.get_private_key_data()
|
||||||
self.privatekey_bytes = privatekey_data
|
self.privatekey_bytes = privatekey_data
|
||||||
|
else:
|
||||||
|
self.module.deprecate(
|
||||||
|
'Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0'
|
||||||
|
' to behave the same as without check mode. You can get that behavior right now'
|
||||||
|
' by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this'
|
||||||
|
' breaks your use-case of this module, please create an issue in the'
|
||||||
|
' community.crypto repository',
|
||||||
|
version='3.0.0',
|
||||||
|
collection_name='community.crypto',
|
||||||
|
)
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
|
|||||||
@@ -11,15 +11,18 @@ __metaclass__ = type
|
|||||||
class ModuleDocFragment(object):
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
# Standard files documentation fragment
|
# Standard files documentation fragment
|
||||||
|
#
|
||||||
|
# NOTE: This document fragment is DEPRECATED and will be removed from community.crypto 3.0.0.
|
||||||
|
# Use both the BASIC and ACCOUNT fragments as a replacement.
|
||||||
DOCUMENTATION = r'''
|
DOCUMENTATION = r'''
|
||||||
notes:
|
notes:
|
||||||
- "If a new enough version of the C(cryptography) library
|
- "If a new enough version of the C(cryptography) library
|
||||||
is available (see Requirements for details), it will be used
|
is available (see Requirements for details), it will be used
|
||||||
instead of the C(openssl) binary. This can be explicitly disabled
|
instead of the C(openssl) binary. This can be explicitly disabled
|
||||||
or enabled with the C(select_crypto_backend) option. Note that using
|
or enabled with the O(select_crypto_backend) option. Note that using
|
||||||
the C(openssl) binary will be slower and less secure, as private key
|
the C(openssl) binary will be slower and less secure, as private key
|
||||||
contents always have to be stored on disk (see
|
contents always have to be stored on disk (see
|
||||||
C(account_key_content))."
|
O(account_key_content))."
|
||||||
- "Although the defaults are chosen so that the module can be used with
|
- "Although the defaults are chosen so that the module can be used with
|
||||||
the L(Let's Encrypt,https://letsencrypt.org/) CA, the module can in
|
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
|
principle be used with any CA providing an ACME endpoint, such as
|
||||||
@@ -47,15 +50,15 @@ options:
|
|||||||
RSA keys can be created with C(openssl genrsa ...). Elliptic curve keys
|
RSA keys can be created with C(openssl genrsa ...). Elliptic curve keys
|
||||||
can be created with C(openssl ecparam -genkey ...). Any other tool creating
|
can be created with C(openssl ecparam -genkey ...). Any other tool creating
|
||||||
private keys in PEM format can be used as well."
|
private keys in PEM format can be used as well."
|
||||||
- "Mutually exclusive with C(account_key_content)."
|
- "Mutually exclusive with O(account_key_content)."
|
||||||
- "Required if C(account_key_content) is not used."
|
- "Required if O(account_key_content) is not used."
|
||||||
type: path
|
type: path
|
||||||
aliases: [ account_key ]
|
aliases: [ account_key ]
|
||||||
account_key_content:
|
account_key_content:
|
||||||
description:
|
description:
|
||||||
- "Content of the ACME account RSA or Elliptic Curve key."
|
- "Content of the ACME account RSA or Elliptic Curve key."
|
||||||
- "Mutually exclusive with C(account_key_src)."
|
- "Mutually exclusive with O(account_key_src)."
|
||||||
- "Required if C(account_key_src) is not used."
|
- "Required if O(account_key_src) is not used."
|
||||||
- "B(Warning:) the content will be written into a temporary file, which will
|
- "B(Warning:) the content will be written into a temporary file, which will
|
||||||
be deleted by Ansible when the module completes. Since this is an
|
be deleted by Ansible when the module completes. Since this is an
|
||||||
important private key — it can be used to change the account key,
|
important private key — it can be used to change the account key,
|
||||||
@@ -81,9 +84,9 @@ options:
|
|||||||
acme_version:
|
acme_version:
|
||||||
description:
|
description:
|
||||||
- "The ACME version of the endpoint."
|
- "The ACME version of the endpoint."
|
||||||
- "Must be C(1) for the classic Let's Encrypt and Buypass ACME endpoints,
|
- "Must be V(1) for the classic Let's Encrypt and Buypass ACME endpoints,
|
||||||
or C(2) for standardized ACME v2 endpoints."
|
or V(2) for standardized ACME v2 endpoints."
|
||||||
- "The value C(1) is deprecated since community.crypto 2.0.0 and will be
|
- "The value V(1) is deprecated since community.crypto 2.0.0 and will be
|
||||||
removed from community.crypto 3.0.0."
|
removed from community.crypto 3.0.0."
|
||||||
required: true
|
required: true
|
||||||
type: int
|
type: int
|
||||||
@@ -114,17 +117,17 @@ options:
|
|||||||
validate_certs:
|
validate_certs:
|
||||||
description:
|
description:
|
||||||
- Whether calls to the ACME directory will validate TLS certificates.
|
- Whether calls to the ACME directory will validate TLS certificates.
|
||||||
- "B(Warning:) Should B(only ever) be set to C(false) for testing purposes,
|
- "B(Warning:) Should B(only ever) be set to V(false) for testing purposes,
|
||||||
for example when testing against a local Pebble server."
|
for example when testing against a local Pebble server."
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to
|
- The default choice is V(auto), which tries to use C(cryptography) if available, and falls back to
|
||||||
C(openssl).
|
C(openssl).
|
||||||
- If set to C(openssl), will try to use the C(openssl) binary.
|
- If set to V(openssl), will try to use the C(openssl) binary.
|
||||||
- If set to C(cryptography), will try to use the
|
- If set to V(cryptography), will try to use the
|
||||||
L(cryptography,https://cryptography.io/) library.
|
L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
@@ -137,3 +140,178 @@ options:
|
|||||||
default: 10
|
default: 10
|
||||||
version_added: 2.3.0
|
version_added: 2.3.0
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# Basic documentation fragment without account data
|
||||||
|
BASIC = r'''
|
||||||
|
notes:
|
||||||
|
- "Although the defaults are chosen so that the module can be used with
|
||||||
|
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:
|
||||||
|
- either openssl or L(cryptography,https://cryptography.io/) >= 1.5
|
||||||
|
- ipaddress
|
||||||
|
options:
|
||||||
|
acme_version:
|
||||||
|
description:
|
||||||
|
- "The ACME version of the endpoint."
|
||||||
|
- "Must be V(1) for the classic Let's Encrypt and Buypass ACME endpoints,
|
||||||
|
or V(2) for standardized ACME v2 endpoints."
|
||||||
|
- "The value V(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:
|
||||||
|
description:
|
||||||
|
- "The ACME directory to use. This is the entry point URL to access
|
||||||
|
the ACME CA server API."
|
||||||
|
- "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."
|
||||||
|
- "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)."
|
||||||
|
- "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)."
|
||||||
|
- "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 V(false) for testing purposes,
|
||||||
|
for example when testing against a local Pebble server."
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
select_crypto_backend:
|
||||||
|
description:
|
||||||
|
- Determines which crypto backend to use.
|
||||||
|
- The default choice is V(auto), which tries to use C(cryptography) if available, and falls back to
|
||||||
|
C(openssl).
|
||||||
|
- If set to V(openssl), will try to use the C(openssl) binary.
|
||||||
|
- If set to V(cryptography), will try to use the
|
||||||
|
L(cryptography,https://cryptography.io/) library.
|
||||||
|
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
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Account data documentation fragment
|
||||||
|
ACCOUNT = r'''
|
||||||
|
notes:
|
||||||
|
- "If a new enough version of the C(cryptography) library
|
||||||
|
is available (see Requirements for details), it will be used
|
||||||
|
instead of the C(openssl) binary. This can be explicitly disabled
|
||||||
|
or enabled with the O(select_crypto_backend) option. Note that using
|
||||||
|
the C(openssl) binary will be slower and less secure, as private key
|
||||||
|
contents always have to be stored on disk (see
|
||||||
|
O(account_key_content))."
|
||||||
|
options:
|
||||||
|
account_key_src:
|
||||||
|
description:
|
||||||
|
- "Path to a file containing the ACME account RSA or Elliptic Curve
|
||||||
|
key."
|
||||||
|
- "Private keys can be created with the
|
||||||
|
M(community.crypto.openssl_privatekey) or M(community.crypto.openssl_privatekey_pipe)
|
||||||
|
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
|
||||||
|
private keys in PEM format can be used as well."
|
||||||
|
- "Mutually exclusive with O(account_key_content)."
|
||||||
|
- "Required if O(account_key_content) is not used."
|
||||||
|
type: path
|
||||||
|
aliases: [ account_key ]
|
||||||
|
account_key_content:
|
||||||
|
description:
|
||||||
|
- "Content of the ACME account RSA or Elliptic Curve key."
|
||||||
|
- "Mutually exclusive with O(account_key_src)."
|
||||||
|
- "Required if O(account_key_src) is not used."
|
||||||
|
- "B(Warning:) the content will be written into a temporary file, which will
|
||||||
|
be deleted by Ansible when the module completes. Since this is an
|
||||||
|
important private key — it can be used to change the account key,
|
||||||
|
or to revoke your certificates without knowing their private keys
|
||||||
|
—, this might not be acceptable."
|
||||||
|
- "In case C(cryptography) is used, the content is not written into a
|
||||||
|
temporary file. It can still happen that it is written to disk by
|
||||||
|
Ansible in the process of moving the module with its argument to
|
||||||
|
the node where it is executed."
|
||||||
|
type: str
|
||||||
|
account_key_passphrase:
|
||||||
|
description:
|
||||||
|
- Phassphrase to use to decode the account key.
|
||||||
|
- "B(Note:) this is not supported by the C(openssl) backend, only by the C(cryptography) backend."
|
||||||
|
type: str
|
||||||
|
version_added: 1.6.0
|
||||||
|
account_uri:
|
||||||
|
description:
|
||||||
|
- "If specified, assumes that the account URI is as given. If the
|
||||||
|
account key does not match this account, or an account with this
|
||||||
|
URI does not exist, the module fails."
|
||||||
|
type: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
# No account data documentation fragment
|
||||||
|
NO_ACCOUNT = r'''
|
||||||
|
notes:
|
||||||
|
- "If a new enough version of the C(cryptography) library
|
||||||
|
is available (see Requirements for details), it will be used
|
||||||
|
instead of the C(openssl) binary. This can be explicitly disabled
|
||||||
|
or enabled with the O(select_crypto_backend) option. Note that using
|
||||||
|
the C(openssl) binary will be slower."
|
||||||
|
options: {}
|
||||||
|
'''
|
||||||
|
|
||||||
|
CERTIFICATE = r'''
|
||||||
|
options:
|
||||||
|
csr:
|
||||||
|
description:
|
||||||
|
- "File containing the CSR for the new certificate."
|
||||||
|
- "Can be created with M(community.crypto.openssl_csr)."
|
||||||
|
- "The CSR may contain multiple Subject Alternate Names, but each one
|
||||||
|
will lead to an individual challenge that must be fulfilled for the
|
||||||
|
CSR to be signed."
|
||||||
|
- "B(Note): the private key used to create the CSR B(must not) be the
|
||||||
|
account key. This is a bad idea from a security point of view, and
|
||||||
|
the CA should not accept the CSR. The ACME server should return an
|
||||||
|
error in this case."
|
||||||
|
- Precisely one of O(csr) or O(csr_content) must be specified.
|
||||||
|
type: path
|
||||||
|
csr_content:
|
||||||
|
description:
|
||||||
|
- "Content of the CSR for the new certificate."
|
||||||
|
- "Can be created with M(community.crypto.openssl_csr_pipe)."
|
||||||
|
- "The CSR may contain multiple Subject Alternate Names, but each one
|
||||||
|
will lead to an individual challenge that must be fulfilled for the
|
||||||
|
CSR to be signed."
|
||||||
|
- "B(Note): the private key used to create the CSR B(must not) be the
|
||||||
|
account key. This is a bad idea from a security point of view, and
|
||||||
|
the CA should not accept the CSR. The ACME server should return an
|
||||||
|
error in this case."
|
||||||
|
- Precisely one of O(csr) or O(csr_content) must be specified.
|
||||||
|
type: str
|
||||||
|
'''
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ description:
|
|||||||
- This module allows one to (re)generate OpenSSL certificates.
|
- This module allows one to (re)generate OpenSSL certificates.
|
||||||
- It uses the cryptography python library to interact with OpenSSL.
|
- It uses the cryptography python library to interact with OpenSSL.
|
||||||
requirements:
|
requirements:
|
||||||
- cryptography >= 1.6 (if using C(selfsigned) or C(ownca) provider)
|
- cryptography >= 1.6 (if using V(selfsigned) or V(ownca) provider)
|
||||||
options:
|
options:
|
||||||
force:
|
force:
|
||||||
description:
|
description:
|
||||||
@@ -28,35 +28,35 @@ options:
|
|||||||
csr_path:
|
csr_path:
|
||||||
description:
|
description:
|
||||||
- Path to the Certificate Signing Request (CSR) used to generate this certificate.
|
- Path to the Certificate Signing Request (CSR) used to generate this certificate.
|
||||||
- This is mutually exclusive with I(csr_content).
|
- This is mutually exclusive with O(csr_content).
|
||||||
type: path
|
type: path
|
||||||
csr_content:
|
csr_content:
|
||||||
description:
|
description:
|
||||||
- Content of the Certificate Signing Request (CSR) used to generate this certificate.
|
- Content of the Certificate Signing Request (CSR) used to generate this certificate.
|
||||||
- This is mutually exclusive with I(csr_path).
|
- This is mutually exclusive with O(csr_path).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
privatekey_path:
|
privatekey_path:
|
||||||
description:
|
description:
|
||||||
- Path to the private key to use when signing the certificate.
|
- Path to the private key to use when signing the certificate.
|
||||||
- This is mutually exclusive with I(privatekey_content).
|
- This is mutually exclusive with O(privatekey_content).
|
||||||
type: path
|
type: path
|
||||||
privatekey_content:
|
privatekey_content:
|
||||||
description:
|
description:
|
||||||
- Content of the private key to use when signing the certificate.
|
- Content of the private key to use when signing the certificate.
|
||||||
- This is mutually exclusive with I(privatekey_path).
|
- This is mutually exclusive with O(privatekey_path).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
privatekey_passphrase:
|
privatekey_passphrase:
|
||||||
description:
|
description:
|
||||||
- The passphrase for the I(privatekey_path) resp. I(privatekey_content).
|
- The passphrase for the O(privatekey_path) resp. O(privatekey_content).
|
||||||
- This is required if the private key is password protected.
|
- This is required if the private key is password protected.
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
ignore_timestamps:
|
ignore_timestamps:
|
||||||
description:
|
description:
|
||||||
- Whether the "not before" and "not after" timestamps should be ignored for idempotency checks.
|
- 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).
|
- It is better to keep the default value V(true) when using relative timestamps (like V(+0s) for now).
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
version_added: 2.0.0
|
version_added: 2.0.0
|
||||||
@@ -64,8 +64,8 @@ options:
|
|||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -73,7 +73,7 @@ options:
|
|||||||
notes:
|
notes:
|
||||||
- All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern.
|
- All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern.
|
||||||
- Date specified should be UTC. Minutes and seconds are mandatory.
|
- Date specified should be UTC. Minutes and seconds are mandatory.
|
||||||
- For security reason, when you use C(ownca) provider, you should NOT run
|
- For security reason, when you use V(ownca) provider, you should NOT run
|
||||||
M(community.crypto.x509_certificate) on a target machine, but on a dedicated CA machine. It
|
M(community.crypto.x509_certificate) on a target machine, but on a dedicated CA machine. It
|
||||||
is recommended not to store the CA private key on the target machine. Once signed, the
|
is recommended not to store the CA private key on the target machine. Once signed, the
|
||||||
certificate can be moved to the target machine.
|
certificate can be moved to the target machine.
|
||||||
@@ -91,26 +91,26 @@ seealso:
|
|||||||
description:
|
description:
|
||||||
- This module allows one to (re)generate OpenSSL certificates.
|
- This module allows one to (re)generate OpenSSL certificates.
|
||||||
requirements:
|
requirements:
|
||||||
- acme-tiny >= 4.0.0 (if using the C(acme) provider)
|
- acme-tiny >= 4.0.0 (if using the V(acme) provider)
|
||||||
options:
|
options:
|
||||||
acme_accountkey_path:
|
acme_accountkey_path:
|
||||||
description:
|
description:
|
||||||
- The path to the accountkey for the C(acme) provider.
|
- The path to the accountkey for the V(acme) provider.
|
||||||
- This is only used by the C(acme) provider.
|
- This is only used by the V(acme) provider.
|
||||||
type: path
|
type: path
|
||||||
|
|
||||||
acme_challenge_path:
|
acme_challenge_path:
|
||||||
description:
|
description:
|
||||||
- The path to the ACME challenge directory that is served on U(http://<HOST>:80/.well-known/acme-challenge/)
|
- The path to the ACME challenge directory that is served on U(http://<HOST>:80/.well-known/acme-challenge/)
|
||||||
- This is only used by the C(acme) provider.
|
- This is only used by the V(acme) provider.
|
||||||
type: path
|
type: path
|
||||||
|
|
||||||
acme_chain:
|
acme_chain:
|
||||||
description:
|
description:
|
||||||
- Include the intermediate certificate to the generated certificate
|
- Include the intermediate certificate to the generated certificate
|
||||||
- This is only used by the C(acme) provider.
|
- This is only used by the V(acme) provider.
|
||||||
- Note that this is only available for older versions of C(acme-tiny).
|
- 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(true) results in an error.
|
New versions include the chain automatically, and setting O(acme_chain) to V(true) results in an error.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ options:
|
|||||||
entrust_cert_type:
|
entrust_cert_type:
|
||||||
description:
|
description:
|
||||||
- Specify the type of certificate requested.
|
- Specify the type of certificate requested.
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
type: str
|
type: str
|
||||||
default: STANDARD_SSL
|
default: STANDARD_SSL
|
||||||
choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ]
|
choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ]
|
||||||
@@ -135,66 +135,66 @@ options:
|
|||||||
entrust_requester_email:
|
entrust_requester_email:
|
||||||
description:
|
description:
|
||||||
- The email of the requester of the certificate (for tracking purposes).
|
- The email of the requester of the certificate (for tracking purposes).
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
- This is required if the provider is C(entrust).
|
- This is required if the provider is V(entrust).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
entrust_requester_name:
|
entrust_requester_name:
|
||||||
description:
|
description:
|
||||||
- The name of the requester of the certificate (for tracking purposes).
|
- The name of the requester of the certificate (for tracking purposes).
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
- This is required if the provider is C(entrust).
|
- This is required if the provider is V(entrust).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
entrust_requester_phone:
|
entrust_requester_phone:
|
||||||
description:
|
description:
|
||||||
- The phone number of the requester of the certificate (for tracking purposes).
|
- The phone number of the requester of the certificate (for tracking purposes).
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
- This is required if the provider is C(entrust).
|
- This is required if the provider is V(entrust).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
entrust_api_user:
|
entrust_api_user:
|
||||||
description:
|
description:
|
||||||
- The username for authentication to the Entrust Certificate Services (ECS) API.
|
- The username for authentication to the Entrust Certificate Services (ECS) API.
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
- This is required if the provider is C(entrust).
|
- This is required if the provider is V(entrust).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
entrust_api_key:
|
entrust_api_key:
|
||||||
description:
|
description:
|
||||||
- The key (password) for authentication to the Entrust Certificate Services (ECS) API.
|
- The key (password) for authentication to the Entrust Certificate Services (ECS) API.
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
- This is required if the provider is C(entrust).
|
- This is required if the provider is V(entrust).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
entrust_api_client_cert_path:
|
entrust_api_client_cert_path:
|
||||||
description:
|
description:
|
||||||
- The path to the client certificate used to authenticate to the Entrust Certificate Services (ECS) API.
|
- The path to the client certificate used to authenticate to the Entrust Certificate Services (ECS) API.
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
- This is required if the provider is C(entrust).
|
- This is required if the provider is V(entrust).
|
||||||
type: path
|
type: path
|
||||||
|
|
||||||
entrust_api_client_cert_key_path:
|
entrust_api_client_cert_key_path:
|
||||||
description:
|
description:
|
||||||
- The path to the private key of the client certificate used to authenticate to the Entrust Certificate Services (ECS) API.
|
- The path to the private key of the client certificate used to authenticate to the Entrust Certificate Services (ECS) API.
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
- This is required if the provider is C(entrust).
|
- This is required if the provider is V(entrust).
|
||||||
type: path
|
type: path
|
||||||
|
|
||||||
entrust_not_after:
|
entrust_not_after:
|
||||||
description:
|
description:
|
||||||
- The point in time at which the certificate stops being valid.
|
- The point in time at which the certificate stops being valid.
|
||||||
- Time can be specified either as relative time or as an absolute timestamp.
|
- Time can be specified either as relative time or as an absolute timestamp.
|
||||||
- A valid absolute time format is C(ASN.1 TIME) such as C(2019-06-18).
|
- A valid absolute time format is C(ASN.1 TIME) such as V(2019-06-18).
|
||||||
- A valid relative time format is C([+-]timespec) where timespec can be an integer + C([w | d | h | m | s]), such as C(+365d) or C(+32w1d2h)).
|
- A valid relative time format is V([+-]timespec) where timespec can be an integer + C([w | d | h | m | s]), such as V(+365d) or V(+32w1d2h)).
|
||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Note that only the date (day, month, year) is supported for specifying the expiry date of the issued certificate.
|
- Note that only the date (day, month, year) is supported for specifying the expiry date of the issued certificate.
|
||||||
- The full date-time is adjusted to EST (GMT -5:00) before issuance, which may result in a certificate with an expiration date one day
|
- The full date-time is adjusted to EST (GMT -5:00) before issuance, which may result in a certificate with an expiration date one day
|
||||||
earlier than expected if a relative time is used.
|
earlier than expected if a relative time is used.
|
||||||
- The minimum certificate lifetime is 90 days, and maximum is three years.
|
- 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.
|
- 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.
|
- This is only used by the V(entrust) provider.
|
||||||
- Please note that this value is B(not) covered by the I(ignore_timestamps) option.
|
- Please note that this value is B(not) covered by the O(ignore_timestamps) option.
|
||||||
type: str
|
type: str
|
||||||
default: +365d
|
default: +365d
|
||||||
|
|
||||||
@@ -202,60 +202,60 @@ options:
|
|||||||
description:
|
description:
|
||||||
- The path to the specification file defining the Entrust Certificate Services (ECS) API configuration.
|
- The path to the specification file defining the Entrust Certificate Services (ECS) API configuration.
|
||||||
- You can use this to keep a local copy of the specification to avoid downloading it every time the module is used.
|
- You can use this to keep a local copy of the specification to avoid downloading it every time the module is used.
|
||||||
- This is only used by the C(entrust) provider.
|
- This is only used by the V(entrust) provider.
|
||||||
type: path
|
type: path
|
||||||
default: https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml
|
default: https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml
|
||||||
'''
|
'''
|
||||||
|
|
||||||
BACKEND_OWNCA_DOCUMENTATION = r'''
|
BACKEND_OWNCA_DOCUMENTATION = r'''
|
||||||
description:
|
description:
|
||||||
- The C(ownca) provider is intended for generating an OpenSSL certificate signed with your own
|
- The V(ownca) provider is intended for generating an OpenSSL certificate signed with your own
|
||||||
CA (Certificate Authority) certificate (self-signed certificate).
|
CA (Certificate Authority) certificate (self-signed certificate).
|
||||||
options:
|
options:
|
||||||
ownca_path:
|
ownca_path:
|
||||||
description:
|
description:
|
||||||
- Remote absolute path of the CA (Certificate Authority) certificate.
|
- Remote absolute path of the CA (Certificate Authority) certificate.
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
- This is mutually exclusive with I(ownca_content).
|
- This is mutually exclusive with O(ownca_content).
|
||||||
type: path
|
type: path
|
||||||
ownca_content:
|
ownca_content:
|
||||||
description:
|
description:
|
||||||
- Content of the CA (Certificate Authority) certificate.
|
- Content of the CA (Certificate Authority) certificate.
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
- This is mutually exclusive with I(ownca_path).
|
- This is mutually exclusive with O(ownca_path).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
ownca_privatekey_path:
|
ownca_privatekey_path:
|
||||||
description:
|
description:
|
||||||
- Path to the CA (Certificate Authority) private key to use when signing the certificate.
|
- Path to the CA (Certificate Authority) private key to use when signing the certificate.
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
- This is mutually exclusive with I(ownca_privatekey_content).
|
- This is mutually exclusive with O(ownca_privatekey_content).
|
||||||
type: path
|
type: path
|
||||||
ownca_privatekey_content:
|
ownca_privatekey_content:
|
||||||
description:
|
description:
|
||||||
- Content of the CA (Certificate Authority) private key to use when signing the certificate.
|
- Content of the CA (Certificate Authority) private key to use when signing the certificate.
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
- This is mutually exclusive with I(ownca_privatekey_path).
|
- This is mutually exclusive with O(ownca_privatekey_path).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
ownca_privatekey_passphrase:
|
ownca_privatekey_passphrase:
|
||||||
description:
|
description:
|
||||||
- The passphrase for the I(ownca_privatekey_path) resp. I(ownca_privatekey_content).
|
- The passphrase for the O(ownca_privatekey_path) resp. O(ownca_privatekey_content).
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
ownca_digest:
|
ownca_digest:
|
||||||
description:
|
description:
|
||||||
- The digest algorithm to be used for the C(ownca) certificate.
|
- The digest algorithm to be used for the V(ownca) certificate.
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
type: str
|
type: str
|
||||||
default: sha256
|
default: sha256
|
||||||
|
|
||||||
ownca_version:
|
ownca_version:
|
||||||
description:
|
description:
|
||||||
- The version of the C(ownca) certificate.
|
- The version of the V(ownca) certificate.
|
||||||
- Nowadays it should almost always be C(3).
|
- Nowadays it should almost always be V(3).
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
type: int
|
type: int
|
||||||
default: 3
|
default: 3
|
||||||
|
|
||||||
@@ -265,12 +265,12 @@ options:
|
|||||||
- Time can be specified either as relative time or as absolute timestamp.
|
- Time can be specified either as relative time or as absolute timestamp.
|
||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
|
||||||
- If this value is not specified, the certificate will start being valid from now.
|
- If this value is not specified, the certificate will start being valid from now.
|
||||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
- 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
|
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
|
||||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
avoid relative timestamps when setting O(ignore_timestamps=false).
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
type: str
|
type: str
|
||||||
default: +0s
|
default: +0s
|
||||||
|
|
||||||
@@ -280,12 +280,12 @@ options:
|
|||||||
- Time can be specified either as relative time or as absolute timestamp.
|
- Time can be specified either as relative time or as absolute timestamp.
|
||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
|
||||||
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
||||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
- 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
|
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
|
||||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
avoid relative timestamps when setting O(ignore_timestamps=false).
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
||||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||||
type: str
|
type: str
|
||||||
@@ -294,12 +294,12 @@ options:
|
|||||||
ownca_create_subject_key_identifier:
|
ownca_create_subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- Whether to create the Subject Key Identifier (SKI) from the public key.
|
- Whether to create the Subject Key Identifier (SKI) from the public key.
|
||||||
- A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not
|
- A value of V(create_if_not_provided) (default) only creates a SKI when the CSR does not
|
||||||
provide one.
|
provide one.
|
||||||
- A value of C(always_create) always creates a SKI. If the CSR provides one, that one is
|
- A value of V(always_create) always creates a SKI. If the CSR provides one, that one is
|
||||||
ignored.
|
ignored.
|
||||||
- A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used.
|
- A value of V(never_create) never creates a SKI. If the CSR provides one, that one is used.
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
- Note that this is only supported if the C(cryptography) backend is used!
|
- Note that this is only supported if the C(cryptography) backend is used!
|
||||||
type: str
|
type: str
|
||||||
choices: [create_if_not_provided, always_create, never_create]
|
choices: [create_if_not_provided, always_create, never_create]
|
||||||
@@ -311,7 +311,7 @@ options:
|
|||||||
a authority key identifier, it is ignored.
|
a authority key identifier, it is ignored.
|
||||||
- The Authority Key Identifier is generated from the CA certificate's Subject Key Identifier,
|
- The Authority Key Identifier is generated from the CA certificate's Subject Key Identifier,
|
||||||
if available. If it is not available, the CA certificate's public key will be used.
|
if available. If it is not available, the CA certificate's public key will be used.
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the V(ownca) provider.
|
||||||
- Note that this is only supported if the C(cryptography) backend is used!
|
- Note that this is only supported if the C(cryptography) backend is used!
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
@@ -319,7 +319,7 @@ options:
|
|||||||
|
|
||||||
BACKEND_SELFSIGNED_DOCUMENTATION = r'''
|
BACKEND_SELFSIGNED_DOCUMENTATION = r'''
|
||||||
notes:
|
notes:
|
||||||
- For the C(selfsigned) provider, I(csr_path) and I(csr_content) are optional. If not provided, a
|
- For the V(selfsigned) provider, O(csr_path) and O(csr_content) are optional. If not provided, a
|
||||||
certificate without any information (Subject, Subject Alternative Names, Key Usage, etc.) is created.
|
certificate without any information (Subject, Subject Alternative Names, Key Usage, etc.) is created.
|
||||||
|
|
||||||
options:
|
options:
|
||||||
@@ -329,28 +329,28 @@ options:
|
|||||||
|
|
||||||
# csr_path:
|
# csr_path:
|
||||||
# description:
|
# description:
|
||||||
# - This is optional for the C(selfsigned) provider. If not provided, a certificate
|
# - This is optional for the V(selfsigned) provider. If not provided, a certificate
|
||||||
# without any information (Subject, Subject Alternative Names, Key Usage, etc.) is
|
# without any information (Subject, Subject Alternative Names, Key Usage, etc.) is
|
||||||
# created.
|
# created.
|
||||||
|
|
||||||
# csr_content:
|
# csr_content:
|
||||||
# description:
|
# description:
|
||||||
# - This is optional for the C(selfsigned) provider. If not provided, a certificate
|
# - This is optional for the V(selfsigned) provider. If not provided, a certificate
|
||||||
# without any information (Subject, Subject Alternative Names, Key Usage, etc.) is
|
# without any information (Subject, Subject Alternative Names, Key Usage, etc.) is
|
||||||
# created.
|
# created.
|
||||||
|
|
||||||
selfsigned_version:
|
selfsigned_version:
|
||||||
description:
|
description:
|
||||||
- Version of the C(selfsigned) certificate.
|
- Version of the V(selfsigned) certificate.
|
||||||
- Nowadays it should almost always be C(3).
|
- Nowadays it should almost always be V(3).
|
||||||
- This is only used by the C(selfsigned) provider.
|
- This is only used by the V(selfsigned) provider.
|
||||||
type: int
|
type: int
|
||||||
default: 3
|
default: 3
|
||||||
|
|
||||||
selfsigned_digest:
|
selfsigned_digest:
|
||||||
description:
|
description:
|
||||||
- Digest algorithm to be used when self-signing the certificate.
|
- Digest algorithm to be used when self-signing the certificate.
|
||||||
- This is only used by the C(selfsigned) provider.
|
- This is only used by the V(selfsigned) provider.
|
||||||
type: str
|
type: str
|
||||||
default: sha256
|
default: sha256
|
||||||
|
|
||||||
@@ -360,12 +360,12 @@ options:
|
|||||||
- Time can be specified either as relative time or as absolute timestamp.
|
- Time can be specified either as relative time or as absolute timestamp.
|
||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
|
||||||
- If this value is not specified, the certificate will start being valid from now.
|
- If this value is not specified, the certificate will start being valid from now.
|
||||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
- 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
|
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
|
||||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
avoid relative timestamps when setting O(ignore_timestamps=false).
|
||||||
- This is only used by the C(selfsigned) provider.
|
- This is only used by the V(selfsigned) provider.
|
||||||
type: str
|
type: str
|
||||||
default: +0s
|
default: +0s
|
||||||
aliases: [ selfsigned_notBefore ]
|
aliases: [ selfsigned_notBefore ]
|
||||||
@@ -376,12 +376,12 @@ options:
|
|||||||
- Time can be specified either as relative time or as absolute timestamp.
|
- Time can be specified either as relative time or as absolute timestamp.
|
||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
|
||||||
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
||||||
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
- 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
|
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
|
||||||
avoid relative timestamps when setting I(ignore_timestamps=false).
|
avoid relative timestamps when setting O(ignore_timestamps=false).
|
||||||
- This is only used by the C(selfsigned) provider.
|
- This is only used by the V(selfsigned) provider.
|
||||||
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
||||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||||
type: str
|
type: str
|
||||||
@@ -391,12 +391,12 @@ options:
|
|||||||
selfsigned_create_subject_key_identifier:
|
selfsigned_create_subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- Whether to create the Subject Key Identifier (SKI) from the public key.
|
- Whether to create the Subject Key Identifier (SKI) from the public key.
|
||||||
- A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not
|
- A value of V(create_if_not_provided) (default) only creates a SKI when the CSR does not
|
||||||
provide one.
|
provide one.
|
||||||
- A value of C(always_create) always creates a SKI. If the CSR provides one, that one is
|
- A value of V(always_create) always creates a SKI. If the CSR provides one, that one is
|
||||||
ignored.
|
ignored.
|
||||||
- A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used.
|
- A value of V(never_create) never creates a SKI. If the CSR provides one, that one is used.
|
||||||
- This is only used by the C(selfsigned) provider.
|
- This is only used by the V(selfsigned) provider.
|
||||||
- Note that this is only supported if the C(cryptography) backend is used!
|
- Note that this is only supported if the C(cryptography) backend is used!
|
||||||
type: str
|
type: str
|
||||||
choices: [create_if_not_provided, always_create, never_create]
|
choices: [create_if_not_provided, always_create, never_create]
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ options:
|
|||||||
privatekey_path:
|
privatekey_path:
|
||||||
description:
|
description:
|
||||||
- The path to the private key to use when signing the certificate signing request.
|
- The path to the private key to use when signing the certificate signing request.
|
||||||
- Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both.
|
- Either O(privatekey_path) or O(privatekey_content) must be specified if O(state) is V(present), but not both.
|
||||||
type: path
|
type: path
|
||||||
privatekey_content:
|
privatekey_content:
|
||||||
description:
|
description:
|
||||||
- The content of the private key to use when signing the certificate signing request.
|
- The content of the private key to use when signing the certificate signing request.
|
||||||
- Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both.
|
- Either O(privatekey_path) or O(privatekey_content) must be specified if O(state) is V(present), but not both.
|
||||||
type: str
|
type: str
|
||||||
privatekey_passphrase:
|
privatekey_passphrase:
|
||||||
description:
|
description:
|
||||||
@@ -53,17 +53,17 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Key/value pairs that will be present in the subject name field of the certificate signing request.
|
- 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 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).
|
- If the order of the components is important, use O(subject_ordered).
|
||||||
- Mutually exclusive with I(subject_ordered).
|
- Mutually exclusive with O(subject_ordered).
|
||||||
type: dict
|
type: dict
|
||||||
subject_ordered:
|
subject_ordered:
|
||||||
description:
|
description:
|
||||||
- A list of dictionaries, where every dictionary must contain one key/value pair. This key/value pair
|
- 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.
|
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.
|
- 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),
|
- Mutually exclusive with O(subject), and any other subject field option, such as O(country_name),
|
||||||
I(state_or_province_name), I(locality_name), I(organization_name), I(organizational_unit_name),
|
O(state_or_province_name), O(locality_name), O(organization_name), O(organizational_unit_name),
|
||||||
I(common_name), or I(email_address).
|
O(common_name), or O(email_address).
|
||||||
type: list
|
type: list
|
||||||
elements: dict
|
elements: dict
|
||||||
version_added: 2.0.0
|
version_added: 2.0.0
|
||||||
@@ -108,8 +108,8 @@ options:
|
|||||||
- Values must be prefixed by their options. (These are C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
- 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).
|
C(otherName), and the ones specific to your CA).
|
||||||
- Note that if no SAN is specified, but a common name, the common
|
- 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
|
name will be added as a SAN except if O(use_common_name_for_san) is
|
||||||
set to I(false).
|
set to V(false).
|
||||||
- More at U(https://tools.ietf.org/html/rfc5280#section-4.2.1.6).
|
- More at U(https://tools.ietf.org/html/rfc5280#section-4.2.1.6).
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -122,8 +122,8 @@ options:
|
|||||||
aliases: [ subjectAltName_critical ]
|
aliases: [ subjectAltName_critical ]
|
||||||
use_common_name_for_san:
|
use_common_name_for_san:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), the module will fill the common name in for
|
- If set to V(true), the module will fill the common name in for
|
||||||
C(subject_alt_name) with C(DNS:) prefix if no SAN is specified.
|
O(subject_alt_name) with C(DNS:) prefix if no SAN is specified.
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
aliases: [ useCommonNameForSAN ]
|
aliases: [ useCommonNameForSAN ]
|
||||||
@@ -186,16 +186,16 @@ options:
|
|||||||
description:
|
description:
|
||||||
- For CA certificates, this specifies a list of identifiers which describe
|
- For CA certificates, this specifies a list of identifiers which describe
|
||||||
subtrees of names that this CA is allowed to issue certificates for.
|
subtrees of names that this CA is allowed to issue certificates for.
|
||||||
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
- Values must be prefixed by their options. (That is, C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||||
C(otherName) and the ones specific to your CA).
|
C(otherName), and the ones specific to your CA).
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
name_constraints_excluded:
|
name_constraints_excluded:
|
||||||
description:
|
description:
|
||||||
- For CA certificates, this specifies a list of identifiers which describe
|
- For CA certificates, this specifies a list of identifiers which describe
|
||||||
subtrees of names that this CA is B(not) allowed to issue certificates for.
|
subtrees of names that this CA is B(not) allowed to issue certificates for.
|
||||||
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
- Values must be prefixed by their options. (That is, C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||||
C(otherName) and the ones specific to your CA).
|
C(otherName), and the ones specific to your CA).
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
name_constraints_critical:
|
name_constraints_critical:
|
||||||
@@ -206,8 +206,8 @@ options:
|
|||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -223,49 +223,51 @@ options:
|
|||||||
subject_key_identifier:
|
subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The subject key identifier as a hex string, where two bytes are separated by colons.
|
- The subject key identifier as a hex string, where two bytes are separated by colons.
|
||||||
- "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
|
- "Example: V(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
|
||||||
- "Please note that commercial CAs ignore this value, respectively use a value of their
|
- "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
|
own choice. Specifying this option is mostly useful for self-signed certificates
|
||||||
or for own CAs."
|
or for own CAs."
|
||||||
- Note that this option can only be used if I(create_subject_key_identifier) is C(false).
|
- Note that this option can only be used if O(create_subject_key_identifier) is V(false).
|
||||||
- Note that this is only supported if the C(cryptography) backend is used!
|
- Note that this is only supported if the C(cryptography) backend is used!
|
||||||
type: str
|
type: str
|
||||||
authority_key_identifier:
|
authority_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The authority key identifier as a hex string, where two bytes are separated by colons.
|
- The authority key identifier as a hex string, where two bytes are separated by colons.
|
||||||
- "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
|
- "Example: V(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
|
||||||
- "Please note that commercial CAs ignore this value, respectively use a value of their
|
- "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
|
own choice. Specifying this option is mostly useful for self-signed certificates
|
||||||
or for own CAs."
|
or for own CAs."
|
||||||
- Note that this is only supported if the C(cryptography) backend is used!
|
- Note that this is only supported if the C(cryptography) backend is used!
|
||||||
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of I(authority_key_identifier),
|
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of O(authority_key_identifier),
|
||||||
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
|
O(authority_cert_issuer) and O(authority_cert_serial_number) is specified.
|
||||||
type: str
|
type: str
|
||||||
authority_cert_issuer:
|
authority_cert_issuer:
|
||||||
description:
|
description:
|
||||||
- Names that will be present in the authority cert issuer field of the certificate signing request.
|
- Names that will be present in the authority cert issuer field of the certificate signing request.
|
||||||
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
- Values must be prefixed by their options. (That is, C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||||
C(otherName) and the ones specific to your CA)
|
C(otherName), and the ones specific to your CA)
|
||||||
- "Example: C(DNS:ca.example.org)"
|
- "Example: V(DNS:ca.example.org)"
|
||||||
- If specified, I(authority_cert_serial_number) must also be specified.
|
- If specified, O(authority_cert_serial_number) must also be specified.
|
||||||
- "Please note that commercial CAs ignore this value, respectively use a value of their
|
- "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
|
own choice. Specifying this option is mostly useful for self-signed certificates
|
||||||
or for own CAs."
|
or for own CAs."
|
||||||
- Note that this is only supported if the C(cryptography) backend is used!
|
- Note that this is only supported if the C(cryptography) backend is used!
|
||||||
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of I(authority_key_identifier),
|
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of O(authority_key_identifier),
|
||||||
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
|
O(authority_cert_issuer) and O(authority_cert_serial_number) is specified.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
authority_cert_serial_number:
|
authority_cert_serial_number:
|
||||||
description:
|
description:
|
||||||
- The authority cert serial number.
|
- The authority cert serial number.
|
||||||
- If specified, I(authority_cert_issuer) must also be specified.
|
- If specified, O(authority_cert_issuer) must also be specified.
|
||||||
- Note that this is only supported if the C(cryptography) backend is used!
|
- Note that this is only supported if the C(cryptography) backend is used!
|
||||||
- "Please note that commercial CAs ignore this value, respectively use a value of their
|
- "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
|
own choice. Specifying this option is mostly useful for self-signed certificates
|
||||||
or for own CAs."
|
or for own CAs."
|
||||||
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of I(authority_key_identifier),
|
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of O(authority_key_identifier),
|
||||||
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
|
O(authority_cert_issuer) and O(authority_cert_serial_number) is specified.
|
||||||
|
- This option accepts an B(integer). If you want to provide serial numbers as colon-separated hex strings,
|
||||||
|
such as C(11:22:33), you need to convert them to an integer with P(community.crypto.parse_serial#filter).
|
||||||
type: int
|
type: int
|
||||||
crl_distribution_points:
|
crl_distribution_points:
|
||||||
description:
|
description:
|
||||||
@@ -277,15 +279,15 @@ options:
|
|||||||
full_name:
|
full_name:
|
||||||
description:
|
description:
|
||||||
- Describes how the CRL can be retrieved.
|
- Describes how the CRL can be retrieved.
|
||||||
- Mutually exclusive with I(relative_name).
|
- Mutually exclusive with O(crl_distribution_points[].relative_name).
|
||||||
- "Example: C(URI:https://ca.example.com/revocations.crl)."
|
- "Example: V(URI:https://ca.example.com/revocations.crl)."
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
relative_name:
|
relative_name:
|
||||||
description:
|
description:
|
||||||
- Describes how the CRL can be retrieved relative to the CRL issuer.
|
- Describes how the CRL can be retrieved relative to the CRL issuer.
|
||||||
- Mutually exclusive with I(full_name).
|
- Mutually exclusive with O(crl_distribution_points[].full_name).
|
||||||
- "Example: C(/CN=example.com)."
|
- "Example: V(/CN=example.com)."
|
||||||
- Can only be used when cryptography >= 1.6 is installed.
|
- Can only be used when cryptography >= 1.6 is installed.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -322,4 +324,6 @@ seealso:
|
|||||||
- module: community.crypto.openssl_privatekey_pipe
|
- module: community.crypto.openssl_privatekey_pipe
|
||||||
- module: community.crypto.openssl_publickey
|
- module: community.crypto.openssl_publickey
|
||||||
- module: community.crypto.openssl_csr_info
|
- module: community.crypto.openssl_csr_info
|
||||||
|
- plugin: community.crypto.parse_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -18,11 +18,6 @@ description:
|
|||||||
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography) or
|
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography) or
|
||||||
L(EdDSA,https://en.wikipedia.org/wiki/EdDSA) private keys.
|
L(EdDSA,https://en.wikipedia.org/wiki/EdDSA) private keys.
|
||||||
- Keys are generated in PEM format.
|
- Keys are generated in PEM format.
|
||||||
- "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."
|
|
||||||
requirements:
|
requirements:
|
||||||
- cryptography >= 1.2.3 (older versions might work as well)
|
- cryptography >= 1.2.3 (older versions might work as well)
|
||||||
options:
|
options:
|
||||||
@@ -34,20 +29,20 @@ options:
|
|||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- The algorithm used to generate the TLS/SSL private key.
|
- The algorithm used to generate the TLS/SSL private key.
|
||||||
- Note that C(ECC), C(X25519), C(X448), C(Ed25519) and C(Ed448) require the C(cryptography) backend.
|
- Note that V(ECC), V(X25519), V(X448), V(Ed25519), and V(Ed448) require the C(cryptography) backend.
|
||||||
C(X25519) needs cryptography 2.5 or newer, while C(X448), C(Ed25519) and C(Ed448) require
|
V(X25519) needs cryptography 2.5 or newer, while V(X448), V(Ed25519), and V(Ed448) require
|
||||||
cryptography 2.6 or newer. For C(ECC), the minimal cryptography version required depends on the
|
cryptography 2.6 or newer. For V(ECC), the minimal cryptography version required depends on the
|
||||||
I(curve) option.
|
O(curve) option.
|
||||||
type: str
|
type: str
|
||||||
default: RSA
|
default: RSA
|
||||||
choices: [ DSA, ECC, Ed25519, Ed448, RSA, X25519, X448 ]
|
choices: [ DSA, ECC, Ed25519, Ed448, RSA, X25519, X448 ]
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- Note that not all curves are supported by all versions of C(cryptography).
|
- Note that not all curves are supported by all versions of C(cryptography).
|
||||||
- For maximal interoperability, C(secp384r1) or C(secp256r1) should be used.
|
- For maximal interoperability, V(secp384r1) or V(secp256r1) should be used.
|
||||||
- We use the curve names as defined in the
|
- We use the curve names as defined in the
|
||||||
L(IANA registry for TLS,https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8).
|
L(IANA registry for TLS,https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8).
|
||||||
- Please note that all curves except C(secp224r1), C(secp256k1), C(secp256r1), C(secp384r1) and C(secp521r1)
|
- Please note that all curves except V(secp224r1), V(secp256k1), V(secp256r1), V(secp384r1), and V(secp521r1)
|
||||||
are discouraged for new private keys.
|
are discouraged for new private keys.
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
@@ -76,13 +71,13 @@ options:
|
|||||||
type: str
|
type: str
|
||||||
cipher:
|
cipher:
|
||||||
description:
|
description:
|
||||||
- The cipher to encrypt the private key. Must be C(auto).
|
- The cipher to encrypt the private key. Must be V(auto).
|
||||||
type: str
|
type: str
|
||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -90,11 +85,11 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format)
|
- Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format)
|
||||||
is used for all keys which support it. Please note that not every key can be exported in any format.
|
is used for all keys which support it. Please note that not every key can be exported in any format.
|
||||||
- The value C(auto) selects a format based on the key format. The value C(auto_ignore) does the same,
|
- The value V(auto) selects a format based on the key format. The value V(auto_ignore) does the same,
|
||||||
but for existing private key files, it will not force a regenerate when its format is not the automatically
|
but for existing private key files, it will not force a regenerate when its format is not the automatically
|
||||||
selected one for generation.
|
selected one for generation.
|
||||||
- Note that if the format for an existing private key mismatches, the key is B(regenerated) by default.
|
- Note that if the format for an existing private key mismatches, the key is B(regenerated) by default.
|
||||||
To change this behavior, use the I(format_mismatch) option.
|
To change this behavior, use the O(format_mismatch) option.
|
||||||
type: str
|
type: str
|
||||||
default: auto_ignore
|
default: auto_ignore
|
||||||
choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ]
|
choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ]
|
||||||
@@ -102,8 +97,8 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Determines behavior of the module if the format of a private key does not match the expected format, but all
|
- Determines behavior of the module if the format of a private key does not match the expected format, but all
|
||||||
other parameters are as expected.
|
other parameters are as expected.
|
||||||
- If set to C(regenerate) (default), generates a new private key.
|
- If set to V(regenerate) (default), generates a new private key.
|
||||||
- If set to C(convert), the key will be converted to the new format instead.
|
- If set to V(convert), the key will be converted to the new format instead.
|
||||||
- Only supported by the C(cryptography) backend.
|
- Only supported by the C(cryptography) backend.
|
||||||
type: str
|
type: str
|
||||||
default: regenerate
|
default: regenerate
|
||||||
@@ -114,24 +109,24 @@ options:
|
|||||||
The module will always generate a new key if the destination file does not exist.
|
The module will always generate a new key if the destination file does not exist.
|
||||||
- By default, the key will be regenerated when it does not 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
|
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)
|
this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if V(full_idempotence)
|
||||||
is specified.
|
is specified.
|
||||||
- If set to C(never), the module will fail if the key cannot be read or the passphrase
|
- If set to V(never), the module will fail if the key cannot be read or the passphrase
|
||||||
is not 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
|
- If set to V(fail), the module will fail if the key does not correspond to the module's
|
||||||
options.
|
options.
|
||||||
- If set to C(partial_idempotence), the key will be regenerated if it does not conform to
|
- If set to V(partial_idempotence), the key will be regenerated if it does not conform to
|
||||||
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
|
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
|
||||||
the key is protected by an unknown passphrase, or when they key is not protected by a
|
the key is protected by an unknown passphrase, or when they key is not protected by a
|
||||||
passphrase, but a passphrase is specified.
|
passphrase, but a passphrase is specified.
|
||||||
- If set to C(full_idempotence), the key will be regenerated if it does not conform to the
|
- If set to V(full_idempotence), the key will be regenerated if it does not conform to the
|
||||||
module's options. This is also the case if the key cannot be read (broken file), the key
|
module's options. This is also the case if the key cannot be read (broken file), the key
|
||||||
is protected by an unknown passphrase, or when they key is not protected by a passphrase,
|
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!
|
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
|
- If set to V(always), the module will always regenerate the key. This is equivalent to
|
||||||
setting I(force) to C(true).
|
setting O(force) to V(true).
|
||||||
- Note that if I(format_mismatch) is set to C(convert) and everything matches except the
|
- Note that if O(format_mismatch) is set to V(convert) and everything matches except the
|
||||||
format, the key will always be converted, except if I(regenerate) is set to C(always).
|
format, the key will always be converted, except if O(regenerate) is set to V(always).
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
- never
|
- never
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ options:
|
|||||||
src_path:
|
src_path:
|
||||||
description:
|
description:
|
||||||
- Name of the file containing the OpenSSL private key to convert.
|
- Name of the file containing the OpenSSL private key to convert.
|
||||||
- Exactly one of I(src_path) or I(src_content) must be specified.
|
- Exactly one of O(src_path) or O(src_content) must be specified.
|
||||||
type: path
|
type: path
|
||||||
src_content:
|
src_content:
|
||||||
description:
|
description:
|
||||||
- The content of the file containing the OpenSSL private key to convert.
|
- 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.
|
- Exactly one of O(src_path) or O(src_content) must be specified.
|
||||||
type: str
|
type: str
|
||||||
src_passphrase:
|
src_passphrase:
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ options:
|
|||||||
name_encoding:
|
name_encoding:
|
||||||
description:
|
description:
|
||||||
- How to encode names (DNS names, URIs, email addresses) in return values.
|
- How to encode names (DNS names, URIs, email addresses) in return values.
|
||||||
- C(ignore) will use the encoding returned by the backend.
|
- V(ignore) will use the encoding returned by the backend.
|
||||||
- C(idna) will convert all labels of domain names to IDNA encoding.
|
- V(idna) will convert all labels of domain names to IDNA encoding.
|
||||||
IDNA2008 will be preferred, and IDNA2003 will be used if IDNA2008 encoding fails.
|
IDNA2008 will be preferred, and IDNA2003 will be used if IDNA2008 encoding fails.
|
||||||
- C(unicode) will convert all labels of domain names to Unicode.
|
- V(unicode) will convert all labels of domain names to Unicode.
|
||||||
IDNA2008 will be preferred, and IDNA2003 will be used if IDNA2008 decoding fails.
|
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.
|
- B(Note) that V(idna) and V(unicode) require the L(idna Python library,https://pypi.org/project/idna/) to be installed.
|
||||||
type: str
|
type: str
|
||||||
default: ignore
|
default: ignore
|
||||||
choices:
|
choices:
|
||||||
@@ -27,5 +27,5 @@ options:
|
|||||||
- idna
|
- idna
|
||||||
- unicode
|
- unicode
|
||||||
requirements:
|
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.
|
- If O(name_encoding) is set to another value than V(ignore), the L(idna Python library,https://pypi.org/project/idna/) needs to be installed.
|
||||||
'''
|
'''
|
||||||
|
|||||||
68
plugins/filter/gpg_fingerprint.py
Normal file
68
plugins/filter/gpg_fingerprint.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2023, 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: gpg_fingerprint
|
||||||
|
short_description: Retrieve a GPG fingerprint from a GPG public or private key
|
||||||
|
author: Felix Fontein (@felixfontein)
|
||||||
|
version_added: 2.15.0
|
||||||
|
description:
|
||||||
|
- "Takes the content of a private or public GPG key as input and returns its fingerprint."
|
||||||
|
options:
|
||||||
|
_input:
|
||||||
|
description:
|
||||||
|
- The content of a GPG public or private key.
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
requirements:
|
||||||
|
- GnuPG (C(gpg) executable)
|
||||||
|
seealso:
|
||||||
|
- plugin: community.crypto.gpg_fingerprint
|
||||||
|
plugin_type: lookup
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Show fingerprint of GPG public key
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ lookup('file', '/path/to/public_key.gpg') | community.crypto.gpg_fingerprint }}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
_value:
|
||||||
|
description:
|
||||||
|
- The fingerprint of the provided public or private GPG key.
|
||||||
|
type: string
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.gnupg.cli import GPGError, get_fingerprint_from_bytes
|
||||||
|
from ansible_collections.community.crypto.plugins.plugin_utils.gnupg import PluginGPGRunner
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_fingerprint(input):
|
||||||
|
if not isinstance(input, string_types):
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
'The input for the community.crypto.gpg_fingerprint filter must be a string; got {type} instead'.format(type=type(input))
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
gpg = PluginGPGRunner()
|
||||||
|
return get_fingerprint_from_bytes(gpg, to_bytes(input))
|
||||||
|
except GPGError as exc:
|
||||||
|
raise AnsibleFilterError(to_native(exc))
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
'''Ansible jinja2 filters'''
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
return {
|
||||||
|
'gpg_fingerprint': gpg_fingerprint,
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ extends_documentation_fragment:
|
|||||||
- community.crypto.name_encoding
|
- community.crypto.name_encoding
|
||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.openssl_csr_info
|
- module: community.crypto.openssl_csr_info
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
@@ -49,11 +51,11 @@ _value:
|
|||||||
signature_valid:
|
signature_valid:
|
||||||
description:
|
description:
|
||||||
- Whether the CSR's signature is valid.
|
- Whether the CSR's signature is valid.
|
||||||
- In case the check returns C(false), the module will fail.
|
- In case the check returns V(false), the module will fail.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
basic_constraints:
|
basic_constraints:
|
||||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
description: Entries in the C(basic_constraints) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -63,7 +65,7 @@ _value:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
extended_key_usage:
|
extended_key_usage:
|
||||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(extended_key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -94,7 +96,7 @@ _value:
|
|||||||
sample: "MAMCAQU="
|
sample: "MAMCAQU="
|
||||||
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
||||||
key_usage:
|
key_usage:
|
||||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: [Key Agreement, Data Encipherment]
|
sample: [Key Agreement, Data Encipherment]
|
||||||
@@ -104,8 +106,8 @@ _value:
|
|||||||
type: bool
|
type: bool
|
||||||
subject_alt_name:
|
subject_alt_name:
|
||||||
description:
|
description:
|
||||||
- Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
- Entries in the C(subject_alt_name) extension, or V(none) if extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -115,7 +117,7 @@ _value:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple:
|
ocsp_must_staple:
|
||||||
description: C(true) if the OCSP Must Staple extension is present, C(none) otherwise.
|
description: V(true) if the OCSP Must Staple extension is present, V(none) otherwise.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple_critical:
|
ocsp_must_staple_critical:
|
||||||
@@ -131,8 +133,8 @@ _value:
|
|||||||
name_constraints_excluded:
|
name_constraints_excluded:
|
||||||
description:
|
description:
|
||||||
- List of excluded subtrees the CA cannot sign certificates for.
|
- List of excluded subtrees the CA cannot sign certificates for.
|
||||||
- Is C(none) if extension is not present.
|
- Is V(none) if extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -140,7 +142,7 @@ _value:
|
|||||||
name_constraints_critical:
|
name_constraints_critical:
|
||||||
description:
|
description:
|
||||||
- Whether the C(name_constraints) extension is critical.
|
- Whether the C(name_constraints) extension is critical.
|
||||||
- Is C(none) if extension is not present.
|
- Is V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
subject:
|
subject:
|
||||||
@@ -164,7 +166,7 @@ _value:
|
|||||||
public_key_type:
|
public_key_type:
|
||||||
description:
|
description:
|
||||||
- The CSR's public key's type.
|
- The CSR's public key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with C(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
@@ -179,57 +181,58 @@ _value:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=RSA) or RV(_value.public_key_type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(_value.public_key_type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(_value.public_key_type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(_value.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.
|
- For RV(_value.public_key_type=DSA), this is the publicly known group element whose discrete logarithm with
|
||||||
|
respect to C(g) is the private key.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=DSA) or RV(_value.public_key_type=ECC)
|
||||||
public_key_fingerprints:
|
public_key_fingerprints:
|
||||||
description:
|
description:
|
||||||
- Fingerprints of CSR's public key.
|
- Fingerprints of CSR's public key.
|
||||||
@@ -241,24 +244,24 @@ _value:
|
|||||||
subject_key_identifier:
|
subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The CSR's subject key identifier.
|
- The CSR's subject key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
- Is V(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_key_identifier:
|
authority_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The CSR's authority key identifier.
|
- The CSR's authority key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_cert_issuer:
|
authority_cert_issuer:
|
||||||
description:
|
description:
|
||||||
- The CSR's authority cert issuer as a list of general names.
|
- The CSR's authority cert issuer as a list of general names.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -266,7 +269,9 @@ _value:
|
|||||||
authority_cert_serial_number:
|
authority_cert_serial_number:
|
||||||
description:
|
description:
|
||||||
- The CSR's authority cert serial number.
|
- The CSR's authority cert serial number.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 12345
|
sample: 12345
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ options:
|
|||||||
return_private_key_data:
|
return_private_key_data:
|
||||||
description:
|
description:
|
||||||
- Whether to return private key data.
|
- Whether to return private key data.
|
||||||
- Only set this to C(true) when you want private information about this key to
|
- Only set this to V(true) when you want private information about this key to
|
||||||
be extracted.
|
be extracted.
|
||||||
- "B(WARNING:) you have to make sure that private key data is not accidentally logged!"
|
- "B(WARNING:) you have to make sure that private key data is not accidentally logged!"
|
||||||
type: bool
|
type: bool
|
||||||
@@ -74,8 +74,8 @@ _value:
|
|||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- The key's type.
|
- The key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with V(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: RSA
|
sample: RSA
|
||||||
@@ -89,61 +89,62 @@ _value:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA) or C(type=DSA)
|
returned: When RV(_value.type=RSA) or RV(_value.type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(_value.type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(_value.type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(_value.type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(_value.type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(_value.type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(type=ECC)
|
returned: When RV(_value.type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(_value.type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(_value.type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(_value.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.
|
- For RV(_value.type=DSA), this is the publicly known group element whose discrete logarithm with
|
||||||
|
respect to C(g) is the private key.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA) or C(type=ECC)
|
returned: When RV(_value.type=DSA) or RV(_value.type=ECC)
|
||||||
private_data:
|
private_data:
|
||||||
description:
|
description:
|
||||||
- Private key data. Depends on key type.
|
- Private key data. Depends on key type.
|
||||||
returned: success and when I(return_private_key_data) is set to C(true)
|
returned: success and when O(return_private_key_data) is set to V(true)
|
||||||
type: dict
|
type: dict
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ _value:
|
|||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- The key's type.
|
- The key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with V(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: RSA
|
sample: RSA
|
||||||
@@ -70,57 +70,58 @@ _value:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA) or C(type=DSA)
|
returned: When RV(_value.type=RSA) or RV(_value.type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(_value.type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(_value.type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(_value.type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(_value.type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(_value.type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(type=ECC)
|
returned: When RV(_value.type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(_value.type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(_value.type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(_value.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.
|
- For RV(_value.type=DSA), this is the publicly known group element whose discrete logarithm with
|
||||||
|
respect to C(g) is the private key.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA) or C(type=ECC)
|
returned: When RV(_value.type=DSA) or RV(_value.type=ECC)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.errors import AnsibleFilterError
|
from ansible.errors import AnsibleFilterError
|
||||||
|
|||||||
66
plugins/filter/parse_serial.py
Normal file
66
plugins/filter/parse_serial.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2024, 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: parse_serial
|
||||||
|
short_description: Convert a serial number as a colon-separated list of hex numbers to an integer
|
||||||
|
author: Felix Fontein (@felixfontein)
|
||||||
|
version_added: 2.18.0
|
||||||
|
description:
|
||||||
|
- "Parses a colon-separated list of hex numbers of the form C(00:11:22:33) and returns the corresponding integer."
|
||||||
|
options:
|
||||||
|
_input:
|
||||||
|
description:
|
||||||
|
- A serial number represented as a colon-separated list of hex numbers between 0 and 255.
|
||||||
|
- These numbers are interpreted as the byte presentation of an unsigned integer in network byte order.
|
||||||
|
That is, C(01:00) is interpreted as the integer 256.
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
seealso:
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Parse serial number
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ '11:22:33' | community.crypto.parse_serial }}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
_value:
|
||||||
|
description:
|
||||||
|
- The serial number as an integer.
|
||||||
|
type: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.serial import parse_serial
|
||||||
|
|
||||||
|
|
||||||
|
def parse_serial_filter(input):
|
||||||
|
if not isinstance(input, string_types):
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
'The input for the community.crypto.parse_serial filter must be a string; got {type} instead'.format(type=type(input))
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return parse_serial(to_native(input))
|
||||||
|
except ValueError as exc:
|
||||||
|
raise AnsibleFilterError(to_native(exc))
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
'''Ansible jinja2 filters'''
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
return {
|
||||||
|
'parse_serial': parse_serial_filter,
|
||||||
|
}
|
||||||
68
plugins/filter/to_serial.py
Normal file
68
plugins/filter/to_serial.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2024, 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: to_serial
|
||||||
|
short_description: Convert an integer to a colon-separated list of hex numbers
|
||||||
|
author: Felix Fontein (@felixfontein)
|
||||||
|
version_added: 2.18.0
|
||||||
|
description:
|
||||||
|
- "Converts an integer to a colon-separated list of hex numbers of the form C(00:11:22:33)."
|
||||||
|
options:
|
||||||
|
_input:
|
||||||
|
description:
|
||||||
|
- The non-negative integer to convert.
|
||||||
|
type: int
|
||||||
|
required: true
|
||||||
|
seealso:
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Convert integer to serial number
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ 1234567 | community.crypto.to_serial }}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
_value:
|
||||||
|
description:
|
||||||
|
- A colon-separated list of hexadecimal numbers.
|
||||||
|
- Letters are upper-case, and all numbers have exactly two digits.
|
||||||
|
- The string is never empty. The representation of C(0) is C("00").
|
||||||
|
type: string
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
from ansible.module_utils.six import integer_types
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.serial import to_serial
|
||||||
|
|
||||||
|
|
||||||
|
def to_serial_filter(input):
|
||||||
|
if not isinstance(input, integer_types):
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
'The input for the community.crypto.to_serial filter must be an integer; got {type} instead'.format(type=type(input))
|
||||||
|
)
|
||||||
|
if input < 0:
|
||||||
|
raise AnsibleFilterError('The input for the community.crypto.to_serial filter must not be negative')
|
||||||
|
try:
|
||||||
|
return to_serial(input)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise AnsibleFilterError(to_native(exc))
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
'''Ansible jinja2 filters'''
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
return {
|
||||||
|
'to_serial': to_serial_filter,
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ extends_documentation_fragment:
|
|||||||
- community.crypto.name_encoding
|
- community.crypto.name_encoding
|
||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.x509_certificate_info
|
- module: community.crypto.x509_certificate_info
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
@@ -51,7 +53,7 @@ _value:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
basic_constraints:
|
basic_constraints:
|
||||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
description: Entries in the C(basic_constraints) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -61,7 +63,7 @@ _value:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
extended_key_usage:
|
extended_key_usage:
|
||||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(extended_key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -92,7 +94,7 @@ _value:
|
|||||||
sample: "MAMCAQU="
|
sample: "MAMCAQU="
|
||||||
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
||||||
key_usage:
|
key_usage:
|
||||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: [Key Agreement, Data Encipherment]
|
sample: [Key Agreement, Data Encipherment]
|
||||||
@@ -102,8 +104,8 @@ _value:
|
|||||||
type: bool
|
type: bool
|
||||||
subject_alt_name:
|
subject_alt_name:
|
||||||
description:
|
description:
|
||||||
- Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
- Entries in the C(subject_alt_name) extension, or V(none) if extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -113,7 +115,7 @@ _value:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple:
|
ocsp_must_staple:
|
||||||
description: C(true) if the OCSP Must Staple extension is present, C(none) otherwise.
|
description: V(true) if the OCSP Must Staple extension is present, V(none) otherwise.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple_critical:
|
ocsp_must_staple_critical:
|
||||||
@@ -164,8 +166,8 @@ _value:
|
|||||||
public_key_type:
|
public_key_type:
|
||||||
description:
|
description:
|
||||||
- The certificate's public key's type.
|
- The certificate's public key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with V(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: RSA
|
sample: RSA
|
||||||
@@ -179,57 +181,58 @@ _value:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=RSA) or RV(_value.public_key_type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(_value.public_key_type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(_value.public_key_type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(_value.public_key_type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(_value.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.
|
- For RV(_value.public_key_type=DSA), this is the publicly known group element whose discrete logarithm with
|
||||||
|
respect to C(g) is the private key.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
|
returned: When RV(_value.public_key_type=DSA) or RV(_value.public_key_type=ECC)
|
||||||
public_key_fingerprints:
|
public_key_fingerprints:
|
||||||
description:
|
description:
|
||||||
- Fingerprints of certificate's public key.
|
- Fingerprints of certificate's public key.
|
||||||
@@ -252,7 +255,10 @@ _value:
|
|||||||
type: str
|
type: str
|
||||||
sample: sha256WithRSAEncryption
|
sample: sha256WithRSAEncryption
|
||||||
serial_number:
|
serial_number:
|
||||||
description: The certificate's serial number.
|
description:
|
||||||
|
- The certificate's serial number.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 1234
|
sample: 1234
|
||||||
@@ -264,24 +270,24 @@ _value:
|
|||||||
subject_key_identifier:
|
subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The certificate's subject key identifier.
|
- The certificate's subject key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
- Is V(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_key_identifier:
|
authority_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The certificate's authority key identifier.
|
- The certificate's authority key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_cert_issuer:
|
authority_cert_issuer:
|
||||||
description:
|
description:
|
||||||
- The certificate's authority cert issuer as a list of general names.
|
- The certificate's authority cert issuer as a list of general names.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -289,18 +295,20 @@ _value:
|
|||||||
authority_cert_serial_number:
|
authority_cert_serial_number:
|
||||||
description:
|
description:
|
||||||
- The certificate's authority cert serial number.
|
- The certificate's authority cert serial number.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 12345
|
sample: 12345
|
||||||
ocsp_uri:
|
ocsp_uri:
|
||||||
description: The OCSP responder URI, if included in the certificate. Will be
|
description: The OCSP responder URI, if included in the certificate. Will be
|
||||||
C(none) if no OCSP responder URI is included.
|
V(none) if no OCSP responder URI is included.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
issuer_uri:
|
issuer_uri:
|
||||||
description: The Issuer URI, if included in the certificate. Will be
|
description: The Issuer URI, if included in the certificate. Will be
|
||||||
C(none) if no issuer URI is included.
|
V(none) if no issuer URI is included.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ options:
|
|||||||
required: true
|
required: true
|
||||||
list_revoked_certificates:
|
list_revoked_certificates:
|
||||||
description:
|
description:
|
||||||
- If set to C(false), the list of revoked certificates is not included in the result.
|
- If set to V(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
|
- 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
|
certificates can take some time, including serializing the result as JSON, sending it to
|
||||||
the Ansible controller, and decoding it again.
|
the Ansible controller, and decoding it again.
|
||||||
@@ -35,6 +35,8 @@ extends_documentation_fragment:
|
|||||||
- community.crypto.name_encoding
|
- community.crypto.name_encoding
|
||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.x509_crl_info
|
- module: community.crypto.x509_crl_info
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
@@ -57,15 +59,18 @@ _value:
|
|||||||
contains:
|
contains:
|
||||||
format:
|
format:
|
||||||
description:
|
description:
|
||||||
- Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)).
|
- Whether the CRL is in PEM format (V(pem)) or in DER format (V(der)).
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: pem
|
sample: pem
|
||||||
|
choices:
|
||||||
|
- pem
|
||||||
|
- der
|
||||||
issuer:
|
issuer:
|
||||||
description:
|
description:
|
||||||
- The CRL's issuer.
|
- The CRL's issuer.
|
||||||
- Note that for repeated values, only the last one will be returned.
|
- Note that for repeated values, only the last one will be returned.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: dict
|
type: dict
|
||||||
sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
|
sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
|
||||||
@@ -92,12 +97,15 @@ _value:
|
|||||||
sample: sha256WithRSAEncryption
|
sample: sha256WithRSAEncryption
|
||||||
revoked_certificates:
|
revoked_certificates:
|
||||||
description: List of certificates to be revoked.
|
description: List of certificates to be revoked.
|
||||||
returned: success if I(list_revoked_certificates=true)
|
returned: success if O(list_revoked_certificates=true)
|
||||||
type: list
|
type: list
|
||||||
elements: dict
|
elements: dict
|
||||||
contains:
|
contains:
|
||||||
serial_number:
|
serial_number:
|
||||||
description: Serial number of the certificate.
|
description:
|
||||||
|
- Serial number of the certificate.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
type: int
|
type: int
|
||||||
sample: 1234
|
sample: 1234
|
||||||
revocation_date:
|
revocation_date:
|
||||||
@@ -107,7 +115,7 @@ _value:
|
|||||||
issuer:
|
issuer:
|
||||||
description:
|
description:
|
||||||
- The certificate's issuer.
|
- The certificate's issuer.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
sample: ["DNS:ca.example.org"]
|
sample: ["DNS:ca.example.org"]
|
||||||
@@ -118,11 +126,19 @@ _value:
|
|||||||
reason:
|
reason:
|
||||||
description:
|
description:
|
||||||
- The value for the revocation reason extension.
|
- 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
|
type: str
|
||||||
sample: key_compromise
|
sample: key_compromise
|
||||||
|
choices:
|
||||||
|
- unspecified
|
||||||
|
- key_compromise
|
||||||
|
- ca_compromise
|
||||||
|
- affiliation_changed
|
||||||
|
- superseded
|
||||||
|
- cessation_of_operation
|
||||||
|
- certificate_hold
|
||||||
|
- privilege_withdrawn
|
||||||
|
- aa_compromise
|
||||||
|
- remove_from_crl
|
||||||
reason_critical:
|
reason_critical:
|
||||||
description: Whether the revocation reason extension is critical.
|
description: Whether the revocation reason extension is critical.
|
||||||
type: bool
|
type: bool
|
||||||
|
|||||||
64
plugins/lookup/gpg_fingerprint.py
Normal file
64
plugins/lookup/gpg_fingerprint.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2023, 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: gpg_fingerprint
|
||||||
|
short_description: Retrieve a GPG fingerprint from a GPG public or private key file
|
||||||
|
author: Felix Fontein (@felixfontein)
|
||||||
|
version_added: 2.15.0
|
||||||
|
description:
|
||||||
|
- "Takes a list of filenames pointing to GPG public or private key files. Returns the fingerprints for each of these keys."
|
||||||
|
options:
|
||||||
|
_terms:
|
||||||
|
description:
|
||||||
|
- A path to a GPG public or private key.
|
||||||
|
type: list
|
||||||
|
elements: path
|
||||||
|
required: true
|
||||||
|
requirements:
|
||||||
|
- GnuPG (C(gpg) executable)
|
||||||
|
seealso:
|
||||||
|
- plugin: community.crypto.gpg_fingerprint
|
||||||
|
plugin_type: filter
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Show fingerprint of GPG public key
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ lookup('community.crypto.gpg_fingerprint', '/path/to/public_key.gpg') }}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
_value:
|
||||||
|
description:
|
||||||
|
- The fingerprints of the provided public or private GPG keys.
|
||||||
|
- The list has one entry for every path provided.
|
||||||
|
type: list
|
||||||
|
elements: string
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.plugins.lookup import LookupBase
|
||||||
|
from ansible.errors import AnsibleLookupError
|
||||||
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.gnupg.cli import GPGError, get_fingerprint_from_file
|
||||||
|
from ansible_collections.community.crypto.plugins.plugin_utils.gnupg import PluginGPGRunner
|
||||||
|
|
||||||
|
|
||||||
|
class LookupModule(LookupBase):
|
||||||
|
def run(self, terms, variables=None, **kwargs):
|
||||||
|
self.set_options(direct=kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
gpg = PluginGPGRunner(cwd=self._loader.get_basedir())
|
||||||
|
result = []
|
||||||
|
for path in terms:
|
||||||
|
result.append(get_fingerprint_from_file(gpg, path))
|
||||||
|
return result
|
||||||
|
except GPGError as exc:
|
||||||
|
raise AnsibleLookupError(to_native(exc))
|
||||||
@@ -9,6 +9,8 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
from ansible.module_utils.common._collections_compat import Mapping
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
||||||
ACMEProtocolException,
|
ACMEProtocolException,
|
||||||
ModuleFailException,
|
ModuleFailException,
|
||||||
@@ -62,7 +64,7 @@ class ACMEAccount(object):
|
|||||||
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
|
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
|
||||||
# to see whether the account already exists.
|
# 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.
|
# if onlyReturnExisting is set to true.
|
||||||
created, data = self._new_reg(contact=contact, allow_creation=False)
|
created, data = self._new_reg(contact=contact, allow_creation=False)
|
||||||
if data:
|
if data:
|
||||||
@@ -96,6 +98,9 @@ class ACMEAccount(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
result, info = self.client.send_signed_request(url, new_reg, fail_on_error=False)
|
result, info = self.client.send_signed_request(url, new_reg, fail_on_error=False)
|
||||||
|
if not isinstance(result, Mapping):
|
||||||
|
raise ACMEProtocolException(
|
||||||
|
self.client.module, msg='Invalid account creation reply from ACME server', info=info, content=result)
|
||||||
|
|
||||||
if info['status'] in ([200, 201] if self.client.version == 1 else [201]):
|
if info['status'] in ([200, 201] if self.client.version == 1 else [201]):
|
||||||
# Account did not exist
|
# Account did not exist
|
||||||
@@ -118,8 +123,10 @@ class ACMEAccount(object):
|
|||||||
if 'location' in info:
|
if 'location' in info:
|
||||||
self.client.set_account_uri(info['location'])
|
self.client.set_account_uri(info['location'])
|
||||||
return False, result
|
return False, result
|
||||||
elif info['status'] == 400 and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation:
|
elif info['status'] in (400, 404) and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation:
|
||||||
# Account does not exist (and we did not try to create it)
|
# Account does not exist (and we did not try to create it)
|
||||||
|
# (According to RFC 8555, Section 7.3.1, the HTTP status code MUST be 400.
|
||||||
|
# Unfortunately Digicert does not care and sends 404 instead.)
|
||||||
return False, None
|
return False, None
|
||||||
elif info['status'] == 403 and result['type'] == 'urn:ietf:params:acme:error:unauthorized' and 'deactivated' in (result.get('detail') or ''):
|
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; has not been
|
# Account has been deactivated; currently works for Pebble; has not been
|
||||||
@@ -154,6 +161,9 @@ class ACMEAccount(object):
|
|||||||
# retry as a regular POST (with no changed data) for pre-draft-15 ACME servers
|
# retry as a regular POST (with no changed data) for pre-draft-15 ACME servers
|
||||||
data = {}
|
data = {}
|
||||||
result, info = self.client.send_signed_request(self.client.account_uri, data, fail_on_error=False)
|
result, info = self.client.send_signed_request(self.client.account_uri, data, fail_on_error=False)
|
||||||
|
if not isinstance(result, Mapping):
|
||||||
|
raise ACMEProtocolException(
|
||||||
|
self.client.module, msg='Invalid account data retrieved from ACME server', info=info, content=result)
|
||||||
if info['status'] in (400, 403) and result.get('type') == 'urn:ietf:params:acme:error:unauthorized':
|
if info['status'] in (400, 403) and result.get('type') == 'urn:ietf:params:acme:error:unauthorized':
|
||||||
# Returned when account is deactivated
|
# Returned when account is deactivated
|
||||||
return None
|
return None
|
||||||
@@ -248,5 +258,9 @@ class ACMEAccount(object):
|
|||||||
else:
|
else:
|
||||||
if self.client.version == 1:
|
if self.client.version == 1:
|
||||||
update_request['resource'] = 'reg'
|
update_request['resource'] = 'reg'
|
||||||
account_data, dummy = self.client.send_signed_request(self.client.account_uri, update_request)
|
account_data, info = self.client.send_signed_request(self.client.account_uri, update_request)
|
||||||
|
if not isinstance(account_data, Mapping):
|
||||||
|
raise ACMEProtocolException(
|
||||||
|
self.client.module, msg='Invalid account updating reply from ACME server', info=info, content=account_data)
|
||||||
|
|
||||||
return True, account_data
|
return True, account_data
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ from ansible.module_utils.common.text.converters import to_bytes
|
|||||||
from ansible.module_utils.urls import fetch_url
|
from ansible.module_utils.urls import fetch_url
|
||||||
from ansible.module_utils.six import PY3
|
from ansible.module_utils.six import PY3
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
||||||
OpenSSLCLIBackend,
|
OpenSSLCLIBackend,
|
||||||
)
|
)
|
||||||
@@ -42,7 +44,9 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
|||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||||
|
compute_cert_id,
|
||||||
nopad_b64,
|
nopad_b64,
|
||||||
|
parse_retry_after,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -55,15 +59,19 @@ else:
|
|||||||
IPADDRESS_IMPORT_ERROR = None
|
IPADDRESS_IMPORT_ERROR = None
|
||||||
|
|
||||||
|
|
||||||
RETRY_STATUS_CODES = (408, 429, 503)
|
# -1 usually means connection problems
|
||||||
|
RETRY_STATUS_CODES = (-1, 408, 429, 503)
|
||||||
|
|
||||||
|
RETRY_COUNT = 10
|
||||||
|
|
||||||
|
|
||||||
def _decode_retry(module, response, info, retry_count):
|
def _decode_retry(module, response, info, retry_count):
|
||||||
if info['status'] not in RETRY_STATUS_CODES:
|
if info['status'] not in RETRY_STATUS_CODES:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if retry_count >= 5:
|
if retry_count >= RETRY_COUNT:
|
||||||
raise ACMEProtocolException(module, msg='Giving up after 5 retries', info=info, response=response)
|
raise ACMEProtocolException(
|
||||||
|
module, msg='Giving up after {retry} retries'.format(retry=RETRY_COUNT), 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)
|
# 429 and 503 should have a Retry-After header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
|
||||||
try:
|
try:
|
||||||
@@ -149,6 +157,9 @@ class ACMEDirectory(object):
|
|||||||
self.module, msg='Was not able to obtain nonce, giving up after 5 retries', info=info, response=response)
|
self.module, msg='Was not able to obtain nonce, giving up after 5 retries', info=info, response=response)
|
||||||
retry_count += 1
|
retry_count += 1
|
||||||
|
|
||||||
|
def has_renewal_info_endpoint(self):
|
||||||
|
return 'renewalInfo' in self.directory
|
||||||
|
|
||||||
|
|
||||||
class ACMEClient(object):
|
class ACMEClient(object):
|
||||||
'''
|
'''
|
||||||
@@ -164,9 +175,9 @@ class ACMEClient(object):
|
|||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.version = module.params['acme_version']
|
self.version = module.params['acme_version']
|
||||||
# account_key path and content are mutually exclusive
|
# account_key path and content are mutually exclusive
|
||||||
self.account_key_file = module.params['account_key_src']
|
self.account_key_file = module.params.get('account_key_src')
|
||||||
self.account_key_content = module.params['account_key_content']
|
self.account_key_content = module.params.get('account_key_content')
|
||||||
self.account_key_passphrase = module.params['account_key_passphrase']
|
self.account_key_passphrase = module.params.get('account_key_passphrase')
|
||||||
|
|
||||||
# Grab account URI from module parameters.
|
# Grab account URI from module parameters.
|
||||||
# Make sure empty string is treated as None.
|
# Make sure empty string is treated as None.
|
||||||
@@ -379,24 +390,94 @@ class ACMEClient(object):
|
|||||||
self.module, msg=error_msg, info=info, content=content, content_json=result if parsed_json_result else None)
|
self.module, msg=error_msg, info=info, content=content, content_json=result if parsed_json_result else None)
|
||||||
return result, info
|
return result, info
|
||||||
|
|
||||||
|
def get_renewal_info(
|
||||||
|
self,
|
||||||
|
cert_id=None,
|
||||||
|
cert_info=None,
|
||||||
|
cert_filename=None,
|
||||||
|
cert_content=None,
|
||||||
|
include_retry_after=False,
|
||||||
|
retry_after_relative_with_timezone=True,
|
||||||
|
):
|
||||||
|
if not self.directory.has_renewal_info_endpoint():
|
||||||
|
raise ModuleFailException('The ACME endpoint does not support ACME Renewal Information retrieval')
|
||||||
|
|
||||||
|
if cert_id is None:
|
||||||
|
cert_id = compute_cert_id(self.backend, cert_info=cert_info, cert_filename=cert_filename, cert_content=cert_content)
|
||||||
|
url = '{base}{cert_id}'.format(base=self.directory.directory['renewalInfo'], cert_id=cert_id)
|
||||||
|
|
||||||
|
data, info = self.get_request(url, parse_json_result=True, fail_on_error=True, get_only=True)
|
||||||
|
|
||||||
|
# Include Retry-After header if asked for
|
||||||
|
if include_retry_after and 'retry-after' in info:
|
||||||
|
try:
|
||||||
|
data['retryAfter'] = parse_retry_after(
|
||||||
|
info['retry-after'],
|
||||||
|
relative_with_timezone=retry_after_relative_with_timezone,
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_default_argspec():
|
def get_default_argspec():
|
||||||
'''
|
'''
|
||||||
Provides default argument spec for the options documented in the acme doc fragment.
|
Provides default argument spec for the options documented in the acme doc fragment.
|
||||||
|
|
||||||
|
DEPRECATED: will be removed in community.crypto 3.0.0
|
||||||
'''
|
'''
|
||||||
return dict(
|
return dict(
|
||||||
account_key_src=dict(type='path', aliases=['account_key']),
|
|
||||||
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', required=True),
|
acme_directory=dict(type='str', required=True),
|
||||||
acme_version=dict(type='int', required=True, choices=[1, 2]),
|
acme_version=dict(type='int', required=True, choices=[1, 2]),
|
||||||
validate_certs=dict(type='bool', default=True),
|
validate_certs=dict(type='bool', default=True),
|
||||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
|
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
|
||||||
request_timeout=dict(type='int', default=10),
|
request_timeout=dict(type='int', default=10),
|
||||||
|
account_key_src=dict(type='path', aliases=['account_key']),
|
||||||
|
account_key_content=dict(type='str', no_log=True),
|
||||||
|
account_key_passphrase=dict(type='str', no_log=True),
|
||||||
|
account_uri=dict(type='str'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_default_argspec(
|
||||||
|
with_account=True,
|
||||||
|
require_account_key=True,
|
||||||
|
with_certificate=False,
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Provides default argument spec for the options documented in the acme doc fragment.
|
||||||
|
'''
|
||||||
|
result = ArgumentSpec(
|
||||||
|
argument_spec=dict(
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if with_account:
|
||||||
|
result.update_argspec(
|
||||||
|
account_key_src=dict(type='path', aliases=['account_key']),
|
||||||
|
account_key_content=dict(type='str', no_log=True),
|
||||||
|
account_key_passphrase=dict(type='str', no_log=True),
|
||||||
|
account_uri=dict(type='str'),
|
||||||
|
)
|
||||||
|
if require_account_key:
|
||||||
|
result.update(required_one_of=[['account_key_src', 'account_key_content']])
|
||||||
|
result.update(mutually_exclusive=[['account_key_src', 'account_key_content']])
|
||||||
|
if with_certificate:
|
||||||
|
result.update_argspec(
|
||||||
|
csr=dict(type='path'),
|
||||||
|
csr_content=dict(type='str'),
|
||||||
|
)
|
||||||
|
result.update(
|
||||||
|
required_one_of=[['csr', 'csr_content']],
|
||||||
|
mutually_exclusive=[['csr', 'csr_content']],
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def create_backend(module, needs_acme_v2):
|
def create_backend(module, needs_acme_v2):
|
||||||
if not HAS_IPADDRESS:
|
if not HAS_IPADDRESS:
|
||||||
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR)
|
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import base64
|
|||||||
import binascii
|
import binascii
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||||
@@ -21,7 +20,9 @@ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_
|
|||||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
|
||||||
|
CertificateInformation,
|
||||||
CryptoBackend,
|
CryptoBackend,
|
||||||
|
_parse_acme_timestamp,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.certificates import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.certificates import (
|
||||||
@@ -37,18 +38,40 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.io import re
|
|||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
parse_name_field,
|
OpenSSLObjectError,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
|
||||||
|
convert_int_to_bytes,
|
||||||
|
convert_int_to_hex,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
cryptography_name_to_oid,
|
cryptography_name_to_oid,
|
||||||
|
cryptography_serial_number_of_cert,
|
||||||
|
get_not_valid_after,
|
||||||
|
get_not_valid_before,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
extract_first_pem,
|
extract_first_pem,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||||
|
parse_name_field,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
ensure_utc_timezone,
|
||||||
|
from_epoch_seconds,
|
||||||
|
get_epoch_seconds,
|
||||||
|
get_now_datetime,
|
||||||
|
get_relative_time_option,
|
||||||
|
UTC,
|
||||||
|
)
|
||||||
|
|
||||||
CRYPTOGRAPHY_MINIMAL_VERSION = '1.5'
|
CRYPTOGRAPHY_MINIMAL_VERSION = '1.5'
|
||||||
|
|
||||||
CRYPTOGRAPHY_ERROR = None
|
CRYPTOGRAPHY_ERROR = None
|
||||||
@@ -78,40 +101,6 @@ else:
|
|||||||
CRYPTOGRAPHY_ERROR = traceback.format_exc()
|
CRYPTOGRAPHY_ERROR = traceback.format_exc()
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info[0] >= 3:
|
|
||||||
# Python 3 (and newer)
|
|
||||||
def _count_bytes(n):
|
|
||||||
return (n.bit_length() + 7) // 8 if n > 0 else 0
|
|
||||||
|
|
||||||
def _convert_int_to_bytes(count, no):
|
|
||||||
return no.to_bytes(count, byteorder='big')
|
|
||||||
|
|
||||||
def _pad_hex(n, digits):
|
|
||||||
res = hex(n)[2:]
|
|
||||||
if len(res) < digits:
|
|
||||||
res = '0' * (digits - len(res)) + res
|
|
||||||
return res
|
|
||||||
else:
|
|
||||||
# Python 2
|
|
||||||
def _count_bytes(n):
|
|
||||||
if n <= 0:
|
|
||||||
return 0
|
|
||||||
h = '%x' % n
|
|
||||||
return (len(h) + 1) // 2
|
|
||||||
|
|
||||||
def _convert_int_to_bytes(count, n):
|
|
||||||
h = '%x' % n
|
|
||||||
if len(h) > 2 * count:
|
|
||||||
raise Exception('Number {1} needs more than {0} bytes!'.format(count, n))
|
|
||||||
return ('0' * (2 * count - len(h)) + h).decode('hex')
|
|
||||||
|
|
||||||
def _pad_hex(n, digits):
|
|
||||||
h = '%x' % n
|
|
||||||
if len(h) < digits:
|
|
||||||
h = '0' * (digits - len(h)) + h
|
|
||||||
return h
|
|
||||||
|
|
||||||
|
|
||||||
class CryptographyChainMatcher(ChainMatcher):
|
class CryptographyChainMatcher(ChainMatcher):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_key_identifier(key_identifier, name, criterium_idx, module):
|
def _parse_key_identifier(key_identifier, name, criterium_idx, module):
|
||||||
@@ -197,6 +186,32 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
super(CryptographyBackend, self).__init__(module)
|
super(CryptographyBackend, self).__init__(module)
|
||||||
|
|
||||||
|
def get_now(self):
|
||||||
|
return get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
|
|
||||||
|
def parse_acme_timestamp(self, timestamp_str):
|
||||||
|
return _parse_acme_timestamp(timestamp_str, with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
|
|
||||||
|
def parse_module_parameter(self, value, name):
|
||||||
|
try:
|
||||||
|
return get_relative_time_option(value, name, backend='cryptography', with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
|
except OpenSSLObjectError as exc:
|
||||||
|
raise BackendException(to_native(exc))
|
||||||
|
|
||||||
|
def interpolate_timestamp(self, timestamp_start, timestamp_end, percentage):
|
||||||
|
start = get_epoch_seconds(timestamp_start)
|
||||||
|
end = get_epoch_seconds(timestamp_end)
|
||||||
|
return from_epoch_seconds(start + percentage * (end - start), with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
|
|
||||||
|
def get_utc_datetime(self, *args, **kwargs):
|
||||||
|
kwargs_ext = dict(kwargs)
|
||||||
|
if CRYPTOGRAPHY_TIMEZONE and ('tzinfo' not in kwargs_ext and len(args) < 8):
|
||||||
|
kwargs_ext['tzinfo'] = UTC
|
||||||
|
result = datetime.datetime(*args, **kwargs_ext)
|
||||||
|
if CRYPTOGRAPHY_TIMEZONE and ('tzinfo' in kwargs or len(args) >= 8):
|
||||||
|
result = ensure_utc_timezone(result)
|
||||||
|
return result
|
||||||
|
|
||||||
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
||||||
'''
|
'''
|
||||||
Parses an RSA or Elliptic Curve key file in PEM format and returns key_data.
|
Parses an RSA or Elliptic Curve key file in PEM format and returns key_data.
|
||||||
@@ -223,8 +238,8 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
'alg': 'RS256',
|
'alg': 'RS256',
|
||||||
'jwk': {
|
'jwk': {
|
||||||
"kty": "RSA",
|
"kty": "RSA",
|
||||||
"e": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.e), pk.e)),
|
"e": nopad_b64(convert_int_to_bytes(pk.e)),
|
||||||
"n": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.n), pk.n)),
|
"n": nopad_b64(convert_int_to_bytes(pk.n)),
|
||||||
},
|
},
|
||||||
'hash': 'sha256',
|
'hash': 'sha256',
|
||||||
}
|
}
|
||||||
@@ -260,8 +275,8 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
'jwk': {
|
'jwk': {
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"crv": curve,
|
"crv": curve,
|
||||||
"x": nopad_b64(_convert_int_to_bytes(num_bytes, pk.x)),
|
"x": nopad_b64(convert_int_to_bytes(pk.x, count=num_bytes)),
|
||||||
"y": nopad_b64(_convert_int_to_bytes(num_bytes, pk.y)),
|
"y": nopad_b64(convert_int_to_bytes(pk.y, count=num_bytes)),
|
||||||
},
|
},
|
||||||
'hash': hashalg,
|
'hash': hashalg,
|
||||||
'point_size': point_size,
|
'point_size': point_size,
|
||||||
@@ -288,8 +303,8 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
hashalg = cryptography.hazmat.primitives.hashes.SHA512
|
hashalg = cryptography.hazmat.primitives.hashes.SHA512
|
||||||
ecdsa = cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hashalg())
|
ecdsa = cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hashalg())
|
||||||
r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature(key_data['key_obj'].sign(sign_payload, ecdsa))
|
r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature(key_data['key_obj'].sign(sign_payload, ecdsa))
|
||||||
rr = _pad_hex(r, 2 * key_data['point_size'])
|
rr = convert_int_to_hex(r, 2 * key_data['point_size'])
|
||||||
ss = _pad_hex(s, 2 * key_data['point_size'])
|
ss = convert_int_to_hex(s, 2 * key_data['point_size'])
|
||||||
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
|
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -328,31 +343,51 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||||
|
'''
|
||||||
|
Return a list of requested identifiers (CN and SANs) for the CSR.
|
||||||
|
Each identifier is a pair (type, identifier), where type is either
|
||||||
|
'dns' or 'ip'.
|
||||||
|
|
||||||
|
The list is deduplicated, and if a CNAME is present, it will be returned
|
||||||
|
as the first element in the result.
|
||||||
|
'''
|
||||||
|
if csr_content is None:
|
||||||
|
csr_content = read_file(csr_filename)
|
||||||
|
else:
|
||||||
|
csr_content = to_bytes(csr_content)
|
||||||
|
csr = cryptography.x509.load_pem_x509_csr(csr_content, _cryptography_backend)
|
||||||
|
|
||||||
|
identifiers = set()
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def add_identifier(identifier):
|
||||||
|
if identifier in identifiers:
|
||||||
|
return
|
||||||
|
identifiers.add(identifier)
|
||||||
|
result.append(identifier)
|
||||||
|
|
||||||
|
for sub in csr.subject:
|
||||||
|
if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME:
|
||||||
|
add_identifier(('dns', sub.value))
|
||||||
|
for extension in csr.extensions:
|
||||||
|
if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
||||||
|
for name in extension.value:
|
||||||
|
if isinstance(name, cryptography.x509.DNSName):
|
||||||
|
add_identifier(('dns', name.value))
|
||||||
|
elif isinstance(name, cryptography.x509.IPAddress):
|
||||||
|
add_identifier(('ip', name.value.compressed))
|
||||||
|
else:
|
||||||
|
raise BackendException('Found unsupported SAN identifier {0}'.format(name))
|
||||||
|
return result
|
||||||
|
|
||||||
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||||
'''
|
'''
|
||||||
Return a set of requested identifiers (CN and SANs) for the CSR.
|
Return a set of requested identifiers (CN and SANs) for the CSR.
|
||||||
Each identifier is a pair (type, identifier), where type is either
|
Each identifier is a pair (type, identifier), where type is either
|
||||||
'dns' or 'ip'.
|
'dns' or 'ip'.
|
||||||
'''
|
'''
|
||||||
identifiers = set([])
|
return set(self.get_ordered_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content))
|
||||||
if csr_content is None:
|
|
||||||
csr_content = read_file(csr_filename)
|
|
||||||
else:
|
|
||||||
csr_content = to_bytes(csr_content)
|
|
||||||
csr = cryptography.x509.load_pem_x509_csr(csr_content, _cryptography_backend)
|
|
||||||
for sub in csr.subject:
|
|
||||||
if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME:
|
|
||||||
identifiers.add(('dns', sub.value))
|
|
||||||
for extension in csr.extensions:
|
|
||||||
if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
|
||||||
for name in extension.value:
|
|
||||||
if isinstance(name, cryptography.x509.DNSName):
|
|
||||||
identifiers.add(('dns', name.value))
|
|
||||||
elif isinstance(name, cryptography.x509.IPAddress):
|
|
||||||
identifiers.add(('ip', name.value.compressed))
|
|
||||||
else:
|
|
||||||
raise BackendException('Found unsupported SAN identifier {0}'.format(name))
|
|
||||||
return identifiers
|
|
||||||
|
|
||||||
def get_cert_days(self, cert_filename=None, cert_content=None, now=None):
|
def get_cert_days(self, cert_filename=None, cert_content=None, now=None):
|
||||||
'''
|
'''
|
||||||
@@ -383,11 +418,54 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e))
|
raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e))
|
||||||
|
|
||||||
if now is None:
|
if now is None:
|
||||||
now = datetime.datetime.now()
|
now = self.get_now()
|
||||||
return (cert.not_valid_after - now).days
|
elif CRYPTOGRAPHY_TIMEZONE:
|
||||||
|
now = ensure_utc_timezone(now)
|
||||||
|
return (get_not_valid_after(cert) - now).days
|
||||||
|
|
||||||
def create_chain_matcher(self, criterium):
|
def create_chain_matcher(self, criterium):
|
||||||
'''
|
'''
|
||||||
Given a Criterium object, creates a ChainMatcher object.
|
Given a Criterium object, creates a ChainMatcher object.
|
||||||
'''
|
'''
|
||||||
return CryptographyChainMatcher(criterium, self.module)
|
return CryptographyChainMatcher(criterium, self.module)
|
||||||
|
|
||||||
|
def get_cert_information(self, cert_filename=None, cert_content=None):
|
||||||
|
'''
|
||||||
|
Return some information on a X.509 certificate as a CertificateInformation object.
|
||||||
|
'''
|
||||||
|
if cert_filename is not None:
|
||||||
|
cert_content = read_file(cert_filename)
|
||||||
|
else:
|
||||||
|
cert_content = to_bytes(cert_content)
|
||||||
|
|
||||||
|
# Make sure we have at most one PEM. Otherwise cryptography 36.0.0 will barf.
|
||||||
|
cert_content = to_bytes(extract_first_pem(to_text(cert_content)) or '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend)
|
||||||
|
except Exception as e:
|
||||||
|
if cert_filename is None:
|
||||||
|
raise BackendException('Cannot parse certificate: {0}'.format(e))
|
||||||
|
raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e))
|
||||||
|
|
||||||
|
ski = None
|
||||||
|
try:
|
||||||
|
ext = cert.extensions.get_extension_for_class(cryptography.x509.SubjectKeyIdentifier)
|
||||||
|
ski = ext.value.digest
|
||||||
|
except cryptography.x509.ExtensionNotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
aki = None
|
||||||
|
try:
|
||||||
|
ext = cert.extensions.get_extension_for_class(cryptography.x509.AuthorityKeyIdentifier)
|
||||||
|
aki = ext.value.key_identifier
|
||||||
|
except cryptography.x509.ExtensionNotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return CertificateInformation(
|
||||||
|
not_valid_after=get_not_valid_after(cert),
|
||||||
|
not_valid_before=get_not_valid_before(cert),
|
||||||
|
serial_number=cryptography_serial_number_of_cert(cert),
|
||||||
|
subject_key_identifier=ski,
|
||||||
|
authority_key_identifier=aki,
|
||||||
|
)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import traceback
|
|||||||
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
|
||||||
|
CertificateInformation,
|
||||||
CryptoBackend,
|
CryptoBackend,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +31,8 @@ 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.acme.utils import nopad_b64
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import convert_bytes_to_int
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ipaddress
|
import ipaddress
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -39,6 +42,33 @@ except ImportError:
|
|||||||
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_date(out_text, name, cert_filename_suffix=""):
|
||||||
|
try:
|
||||||
|
date_str = re.search(r"\s+%s\s*:\s+(.*)" % name, out_text).group(1)
|
||||||
|
return datetime.datetime.strptime(date_str, '%b %d %H:%M:%S %Y %Z')
|
||||||
|
except AttributeError:
|
||||||
|
raise BackendException("No '{0}' date found{1}".format(name, cert_filename_suffix))
|
||||||
|
except ValueError as exc:
|
||||||
|
raise BackendException("Failed to parse '{0}' date{1}: {2}".format(name, cert_filename_suffix, exc))
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_octets(octets_text):
|
||||||
|
return binascii.unhexlify(re.sub(r"(\s|:)", "", octets_text).encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_octets(out_text, name, required=True, potential_prefixes=None):
|
||||||
|
regexp = r"\s+%s:\s*\n\s+%s([A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2})*)\s*\n" % (
|
||||||
|
name,
|
||||||
|
('(?:%s)' % '|'.join(re.escape(pp) for pp in potential_prefixes)) if potential_prefixes else '',
|
||||||
|
)
|
||||||
|
match = re.search(regexp, out_text, re.MULTILINE | re.DOTALL)
|
||||||
|
if match is not None:
|
||||||
|
return _decode_octets(match.group(1))
|
||||||
|
if not required:
|
||||||
|
return None
|
||||||
|
raise BackendException("No '{0}' octet string found".format(name))
|
||||||
|
|
||||||
|
|
||||||
class OpenSSLCLIBackend(CryptoBackend):
|
class OpenSSLCLIBackend(CryptoBackend):
|
||||||
def __init__(self, module, openssl_binary=None):
|
def __init__(self, module, openssl_binary=None):
|
||||||
super(OpenSSLCLIBackend, self).__init__(module)
|
super(OpenSSLCLIBackend, self).__init__(module)
|
||||||
@@ -89,10 +119,12 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
dummy, out, dummy = self.module.run_command(
|
dummy, out, dummy = self.module.run_command(
|
||||||
openssl_keydump_cmd, check_rc=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
openssl_keydump_cmd, check_rc=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||||
|
|
||||||
|
out_text = to_text(out, errors='surrogate_or_strict')
|
||||||
|
|
||||||
if account_key_type == 'rsa':
|
if account_key_type == 'rsa':
|
||||||
pub_hex, pub_exp = re.search(
|
pub_hex = re.search(r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent", out_text, re.MULTILINE | re.DOTALL).group(1)
|
||||||
r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)",
|
|
||||||
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL).groups()
|
pub_exp = re.search(r"\npublicExponent: ([0-9]+)", out_text, re.MULTILINE | re.DOTALL).group(1)
|
||||||
pub_exp = "{0:x}".format(int(pub_exp))
|
pub_exp = "{0:x}".format(int(pub_exp))
|
||||||
if len(pub_exp) % 2:
|
if len(pub_exp) % 2:
|
||||||
pub_exp = "0{0}".format(pub_exp)
|
pub_exp = "0{0}".format(pub_exp)
|
||||||
@@ -104,17 +136,19 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
'jwk': {
|
'jwk': {
|
||||||
"kty": "RSA",
|
"kty": "RSA",
|
||||||
"e": nopad_b64(binascii.unhexlify(pub_exp.encode("utf-8"))),
|
"e": nopad_b64(binascii.unhexlify(pub_exp.encode("utf-8"))),
|
||||||
"n": nopad_b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))),
|
"n": nopad_b64(_decode_octets(pub_hex)),
|
||||||
},
|
},
|
||||||
'hash': 'sha256',
|
'hash': 'sha256',
|
||||||
}
|
}
|
||||||
elif account_key_type == 'ec':
|
elif account_key_type == 'ec':
|
||||||
pub_data = re.search(
|
pub_data = re.search(
|
||||||
r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
|
r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
|
||||||
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
|
out_text,
|
||||||
|
re.MULTILINE | re.DOTALL,
|
||||||
|
)
|
||||||
if pub_data is None:
|
if pub_data is None:
|
||||||
raise KeyParsingError('cannot parse elliptic curve key')
|
raise KeyParsingError('cannot parse elliptic curve key')
|
||||||
pub_hex = binascii.unhexlify(re.sub(r"(\s|:)", "", pub_data.group(1)).encode("utf-8"))
|
pub_hex = _decode_octets(pub_data.group(1))
|
||||||
asn1_oid_curve = pub_data.group(2).lower()
|
asn1_oid_curve = pub_data.group(2).lower()
|
||||||
nist_curve = pub_data.group(3).lower() if pub_data.group(3) else None
|
nist_curve = pub_data.group(3).lower() if pub_data.group(3) else None
|
||||||
if asn1_oid_curve == 'prime256v1' or nist_curve == 'p-256':
|
if asn1_oid_curve == 'prime256v1' or nist_curve == 'p-256':
|
||||||
@@ -225,11 +259,14 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
# We do not want to error out on something IPAddress() cannot parse
|
# We do not want to error out on something IPAddress() cannot parse
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||||
'''
|
'''
|
||||||
Return a set of requested identifiers (CN and SANs) for the CSR.
|
Return a list of requested identifiers (CN and SANs) for the CSR.
|
||||||
Each identifier is a pair (type, identifier), where type is either
|
Each identifier is a pair (type, identifier), where type is either
|
||||||
'dns' or 'ip'.
|
'dns' or 'ip'.
|
||||||
|
|
||||||
|
The list is deduplicated, and if a CNAME is present, it will be returned
|
||||||
|
as the first element in the result.
|
||||||
'''
|
'''
|
||||||
filename = csr_filename
|
filename = csr_filename
|
||||||
data = None
|
data = None
|
||||||
@@ -241,24 +278,40 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
dummy, out, dummy = self.module.run_command(
|
dummy, out, dummy = self.module.run_command(
|
||||||
openssl_csr_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
openssl_csr_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||||
|
|
||||||
identifiers = set([])
|
identifiers = set()
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def add_identifier(identifier):
|
||||||
|
if identifier in identifiers:
|
||||||
|
return
|
||||||
|
identifiers.add(identifier)
|
||||||
|
result.append(identifier)
|
||||||
|
|
||||||
common_name = re.search(r"Subject:.* CN\s?=\s?([^\s,;/]+)", to_text(out, errors='surrogate_or_strict'))
|
common_name = re.search(r"Subject:.* CN\s?=\s?([^\s,;/]+)", to_text(out, errors='surrogate_or_strict'))
|
||||||
if common_name is not None:
|
if common_name is not None:
|
||||||
identifiers.add(('dns', common_name.group(1)))
|
add_identifier(('dns', common_name.group(1)))
|
||||||
subject_alt_names = re.search(
|
subject_alt_names = re.search(
|
||||||
r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n",
|
r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n",
|
||||||
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
|
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
|
||||||
if subject_alt_names is not None:
|
if subject_alt_names is not None:
|
||||||
for san in subject_alt_names.group(1).split(", "):
|
for san in subject_alt_names.group(1).split(", "):
|
||||||
if san.lower().startswith("dns:"):
|
if san.lower().startswith("dns:"):
|
||||||
identifiers.add(('dns', san[4:]))
|
add_identifier(('dns', san[4:]))
|
||||||
elif san.lower().startswith("ip:"):
|
elif san.lower().startswith("ip:"):
|
||||||
identifiers.add(('ip', self._normalize_ip(san[3:])))
|
add_identifier(('ip', self._normalize_ip(san[3:])))
|
||||||
elif san.lower().startswith("ip address:"):
|
elif san.lower().startswith("ip address:"):
|
||||||
identifiers.add(('ip', self._normalize_ip(san[11:])))
|
add_identifier(('ip', self._normalize_ip(san[11:])))
|
||||||
else:
|
else:
|
||||||
raise BackendException('Found unsupported SAN identifier "{0}"'.format(san))
|
raise BackendException('Found unsupported SAN identifier "{0}"'.format(san))
|
||||||
return identifiers
|
return result
|
||||||
|
|
||||||
|
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||||
|
'''
|
||||||
|
Return a set of requested identifiers (CN and SANs) for the CSR.
|
||||||
|
Each identifier is a pair (type, identifier), where type is either
|
||||||
|
'dns' or 'ip'.
|
||||||
|
'''
|
||||||
|
return set(self.get_ordered_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content))
|
||||||
|
|
||||||
def get_cert_days(self, cert_filename=None, cert_content=None, now=None):
|
def get_cert_days(self, cert_filename=None, cert_content=None, now=None):
|
||||||
'''
|
'''
|
||||||
@@ -284,13 +337,8 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"]
|
openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"]
|
||||||
dummy, out, dummy = self.module.run_command(
|
dummy, out, dummy = self.module.run_command(
|
||||||
openssl_cert_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
openssl_cert_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||||
try:
|
out_text = to_text(out, errors='surrogate_or_strict')
|
||||||
not_after_str = re.search(r"\s+Not After\s*:\s+(.*)", to_text(out, errors='surrogate_or_strict')).group(1)
|
not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix)
|
||||||
not_after = datetime.datetime.strptime(not_after_str, '%b %d %H:%M:%S %Y %Z')
|
|
||||||
except AttributeError:
|
|
||||||
raise BackendException("No 'Not after' date found{0}".format(cert_filename_suffix))
|
|
||||||
except ValueError:
|
|
||||||
raise BackendException("Failed to parse 'Not after' date{0}".format(cert_filename_suffix))
|
|
||||||
if now is None:
|
if now is None:
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
return (not_after - now).days
|
return (not_after - now).days
|
||||||
@@ -300,3 +348,43 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
Given a Criterium object, creates a ChainMatcher object.
|
Given a Criterium object, creates a ChainMatcher object.
|
||||||
'''
|
'''
|
||||||
raise BackendException('Alternate chain matching can only be used with the "cryptography" backend.')
|
raise BackendException('Alternate chain matching can only be used with the "cryptography" backend.')
|
||||||
|
|
||||||
|
def get_cert_information(self, cert_filename=None, cert_content=None):
|
||||||
|
'''
|
||||||
|
Return some information on a X.509 certificate as a CertificateInformation object.
|
||||||
|
'''
|
||||||
|
filename = cert_filename
|
||||||
|
data = None
|
||||||
|
if cert_filename is not None:
|
||||||
|
cert_filename_suffix = ' in {0}'.format(cert_filename)
|
||||||
|
else:
|
||||||
|
filename = '/dev/stdin'
|
||||||
|
data = to_bytes(cert_content)
|
||||||
|
cert_filename_suffix = ''
|
||||||
|
|
||||||
|
openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"]
|
||||||
|
dummy, out, dummy = self.module.run_command(
|
||||||
|
openssl_cert_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||||
|
out_text = to_text(out, errors='surrogate_or_strict')
|
||||||
|
|
||||||
|
not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix)
|
||||||
|
not_before = _extract_date(out_text, 'Not Before', cert_filename_suffix=cert_filename_suffix)
|
||||||
|
|
||||||
|
sn = re.search(
|
||||||
|
r" Serial Number: ([0-9]+)",
|
||||||
|
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
|
||||||
|
if sn:
|
||||||
|
serial = int(sn.group(1))
|
||||||
|
else:
|
||||||
|
serial = convert_bytes_to_int(_extract_octets(out_text, 'Serial Number', required=True))
|
||||||
|
|
||||||
|
ski = _extract_octets(out_text, 'X509v3 Subject Key Identifier', required=False)
|
||||||
|
aki = _extract_octets(out_text, 'X509v3 Authority Key Identifier', required=False, potential_prefixes=['keyid:', ''])
|
||||||
|
|
||||||
|
return CertificateInformation(
|
||||||
|
not_valid_after=not_after,
|
||||||
|
not_valid_before=not_before,
|
||||||
|
serial_number=serial,
|
||||||
|
subject_key_identifier=ski,
|
||||||
|
authority_key_identifier=aki,
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,9 +9,78 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
import abc
|
import abc
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
||||||
|
BackendException,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
|
OpenSSLObjectError,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
ensure_utc_timezone,
|
||||||
|
from_epoch_seconds,
|
||||||
|
get_epoch_seconds,
|
||||||
|
get_now_datetime,
|
||||||
|
get_relative_time_option,
|
||||||
|
remove_timezone,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CertificateInformation = namedtuple(
|
||||||
|
'CertificateInformation',
|
||||||
|
(
|
||||||
|
'not_valid_after',
|
||||||
|
'not_valid_before',
|
||||||
|
'serial_number',
|
||||||
|
'subject_key_identifier',
|
||||||
|
'authority_key_identifier',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_FRACTIONAL_MATCHER = re.compile(r'^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(|\.\d+)(Z|[+-]\d{2}:?\d{2}.*)$')
|
||||||
|
|
||||||
|
|
||||||
|
def _reduce_fractional_digits(timestamp_str):
|
||||||
|
"""
|
||||||
|
Given a RFC 3339 timestamp that includes too many digits for the fractional seconds part, reduces these to at most 6.
|
||||||
|
"""
|
||||||
|
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||||
|
m = _FRACTIONAL_MATCHER.match(timestamp_str)
|
||||||
|
if not m:
|
||||||
|
raise BackendException('Cannot parse ISO 8601 timestamp {0!r}'.format(timestamp_str))
|
||||||
|
timestamp, fractional, timezone = m.groups()
|
||||||
|
if len(fractional) > 7:
|
||||||
|
# Python does not support anything smaller than microseconds
|
||||||
|
# (Golang supports nanoseconds, Boulder often emits more fractional digits, which Python chokes on)
|
||||||
|
fractional = fractional[:7]
|
||||||
|
return '%s%s%s' % (timestamp, fractional, timezone)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_acme_timestamp(timestamp_str, with_timezone):
|
||||||
|
"""
|
||||||
|
Parses a RFC 3339 timestamp.
|
||||||
|
"""
|
||||||
|
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||||
|
timestamp_str = _reduce_fractional_digits(timestamp_str)
|
||||||
|
for format in ('%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S.%fZ', '%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S.%f%z'):
|
||||||
|
# Note that %z won't work with Python 2... https://stackoverflow.com/a/27829491
|
||||||
|
try:
|
||||||
|
result = datetime.datetime.strptime(timestamp_str, format)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ensure_utc_timezone(result) if with_timezone else remove_timezone(result)
|
||||||
|
raise BackendException('Cannot parse ISO 8601 timestamp {0!r}'.format(timestamp_str))
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
@@ -19,6 +88,30 @@ class CryptoBackend(object):
|
|||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
self.module = module
|
self.module = module
|
||||||
|
|
||||||
|
def get_now(self):
|
||||||
|
return get_now_datetime(with_timezone=False)
|
||||||
|
|
||||||
|
def parse_acme_timestamp(self, timestamp_str):
|
||||||
|
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||||
|
return _parse_acme_timestamp(timestamp_str, with_timezone=False)
|
||||||
|
|
||||||
|
def parse_module_parameter(self, value, name):
|
||||||
|
try:
|
||||||
|
return get_relative_time_option(value, name, backend='cryptography', with_timezone=False)
|
||||||
|
except OpenSSLObjectError as exc:
|
||||||
|
raise BackendException(to_native(exc))
|
||||||
|
|
||||||
|
def interpolate_timestamp(self, timestamp_start, timestamp_end, percentage):
|
||||||
|
start = get_epoch_seconds(timestamp_start)
|
||||||
|
end = get_epoch_seconds(timestamp_end)
|
||||||
|
return from_epoch_seconds(start + percentage * (end - start), with_timezone=False)
|
||||||
|
|
||||||
|
def get_utc_datetime(self, *args, **kwargs):
|
||||||
|
result = datetime.datetime(*args, **kwargs)
|
||||||
|
if 'tzinfo' in kwargs or len(args) >= 8:
|
||||||
|
result = remove_timezone(result)
|
||||||
|
return result
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
||||||
'''
|
'''
|
||||||
@@ -34,6 +127,23 @@ class CryptoBackend(object):
|
|||||||
def create_mac_key(self, alg, key):
|
def create_mac_key(self, alg, key):
|
||||||
'''Create a MAC key.'''
|
'''Create a MAC key.'''
|
||||||
|
|
||||||
|
def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||||
|
'''
|
||||||
|
Return a list of requested identifiers (CN and SANs) for the CSR.
|
||||||
|
Each identifier is a pair (type, identifier), where type is either
|
||||||
|
'dns' or 'ip'.
|
||||||
|
|
||||||
|
The list is deduplicated, and if a CNAME is present, it will be returned
|
||||||
|
as the first element in the result.
|
||||||
|
'''
|
||||||
|
self.module.deprecate(
|
||||||
|
"Every backend must override the get_ordered_csr_identifiers() method."
|
||||||
|
" The default implementation will be removed in 3.0.0 and this method will be marked as `abstractmethod` by then.",
|
||||||
|
version='3.0.0',
|
||||||
|
collection_name='community.crypto',
|
||||||
|
)
|
||||||
|
return sorted(self.get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content))
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||||
'''
|
'''
|
||||||
@@ -57,3 +167,12 @@ class CryptoBackend(object):
|
|||||||
'''
|
'''
|
||||||
Given a Criterium object, creates a ChainMatcher object.
|
Given a Criterium object, creates a ChainMatcher object.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def get_cert_information(self, cert_filename=None, cert_content=None):
|
||||||
|
'''
|
||||||
|
Return some information on a X.509 certificate as a CertificateInformation object.
|
||||||
|
'''
|
||||||
|
# Not implementing this method in a backend is DEPRECATED and will be
|
||||||
|
# disallowed in community.crypto 3.0.0. This method will be marked as
|
||||||
|
# @abstractmethod by then.
|
||||||
|
raise BackendException('This backend does not support get_cert_information()')
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class Challenge(object):
|
|||||||
# https://tools.ietf.org/html/rfc8555#section-8.4
|
# https://tools.ietf.org/html/rfc8555#section-8.4
|
||||||
resource = '_acme-challenge'
|
resource = '_acme-challenge'
|
||||||
value = nopad_b64(hashlib.sha256(to_bytes(key_authorization)).digest())
|
value = nopad_b64(hashlib.sha256(to_bytes(key_authorization)).digest())
|
||||||
record = (resource + identifier[1:]) if identifier.startswith('*.') else '{0}.{1}'.format(resource, identifier)
|
record = '{0}.{1}'.format(resource, identifier[2:] if identifier.startswith('*.') else identifier)
|
||||||
return {
|
return {
|
||||||
'resource': resource,
|
'resource': resource,
|
||||||
'resource_value': value,
|
'resource_value': value,
|
||||||
@@ -283,13 +283,21 @@ class Authorization(object):
|
|||||||
return self.status == 'valid'
|
return self.status == 'valid'
|
||||||
return self.wait_for_validation(client, challenge_type)
|
return self.wait_for_validation(client, challenge_type)
|
||||||
|
|
||||||
|
def can_deactivate(self):
|
||||||
|
'''
|
||||||
|
Deactivates this authorization.
|
||||||
|
https://community.letsencrypt.org/t/authorization-deactivation/19860/2
|
||||||
|
https://tools.ietf.org/html/rfc8555#section-7.5.2
|
||||||
|
'''
|
||||||
|
return self.status in ('valid', 'pending')
|
||||||
|
|
||||||
def deactivate(self, client):
|
def deactivate(self, client):
|
||||||
'''
|
'''
|
||||||
Deactivates this authorization.
|
Deactivates this authorization.
|
||||||
https://community.letsencrypt.org/t/authorization-deactivation/19860/2
|
https://community.letsencrypt.org/t/authorization-deactivation/19860/2
|
||||||
https://tools.ietf.org/html/rfc8555#section-7.5.2
|
https://tools.ietf.org/html/rfc8555#section-7.5.2
|
||||||
'''
|
'''
|
||||||
if self.status != 'valid':
|
if not self.can_deactivate():
|
||||||
return
|
return
|
||||||
authz_deactivate = {
|
authz_deactivate = {
|
||||||
'status': 'deactivated'
|
'status': 'deactivated'
|
||||||
@@ -301,3 +309,21 @@ class Authorization(object):
|
|||||||
self.status = 'deactivated'
|
self.status = 'deactivated'
|
||||||
return True
|
return True
|
||||||
return False
|
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
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ def format_http_status(status_code):
|
|||||||
|
|
||||||
|
|
||||||
def format_error_problem(problem, subproblem_prefix=''):
|
def format_error_problem(problem, subproblem_prefix=''):
|
||||||
|
error_type = problem.get('type', 'about:blank') # https://www.rfc-editor.org/rfc/rfc7807#section-3.1
|
||||||
if 'title' in problem:
|
if 'title' in problem:
|
||||||
msg = 'Error "{title}" ({type})'.format(
|
msg = 'Error "{title}" ({type})'.format(
|
||||||
type=problem['type'],
|
type=error_type,
|
||||||
title=problem['title'],
|
title=problem['title'],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg = 'Error {type}'.format(type=problem['type'])
|
msg = 'Error {type}'.format(type=error_type)
|
||||||
if 'detail' in problem:
|
if 'detail' in problem:
|
||||||
msg += ': "{detail}"'.format(detail=problem['detail'])
|
msg += ': "{detail}"'.format(detail=problem['detail'])
|
||||||
subproblems = problem.get('subproblems')
|
subproblems = problem.get('subproblems')
|
||||||
@@ -95,10 +96,12 @@ class ACMEProtocolException(ModuleFailException):
|
|||||||
extras['http_status'] = code
|
extras['http_status'] = code
|
||||||
if code is not None and code >= 400 and content_json is not None and 'type' in content_json:
|
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:
|
if 'status' in content_json and content_json['status'] != code:
|
||||||
code = 'status {problem_code} (HTTP status: {http_code})'.format(
|
code_msg = 'status {problem_code} (HTTP status: {http_code})'.format(
|
||||||
http_code=format_http_status(code), problem_code=content_json['status'])
|
http_code=format_http_status(code), problem_code=content_json['status'])
|
||||||
else:
|
else:
|
||||||
code = 'status {problem_code}'.format(problem_code=format_http_status(code))
|
code_msg = 'status {problem_code}'.format(problem_code=format_http_status(code))
|
||||||
|
if code == -1 and info.get('msg'):
|
||||||
|
code_msg = 'error: {msg}'.format(msg=info['msg'])
|
||||||
subproblems = content_json.pop('subproblems', None)
|
subproblems = content_json.pop('subproblems', None)
|
||||||
add_msg = ' {problem}.'.format(problem=format_error_problem(content_json))
|
add_msg = ' {problem}.'.format(problem=format_error_problem(content_json))
|
||||||
extras['problem'] = content_json
|
extras['problem'] = content_json
|
||||||
@@ -112,12 +115,14 @@ class ACMEProtocolException(ModuleFailException):
|
|||||||
problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index)),
|
problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index)),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
code = 'HTTP status {code}'.format(code=format_http_status(code))
|
code_msg = 'HTTP status {code}'.format(code=format_http_status(code))
|
||||||
|
if code == -1 and info.get('msg'):
|
||||||
|
code_msg = 'error: {msg}'.format(msg=info['msg'])
|
||||||
if content_json is not None:
|
if content_json is not None:
|
||||||
add_msg = ' The JSON error result: {content}'.format(content=content_json)
|
add_msg = ' The JSON error result: {content}'.format(content=content_json)
|
||||||
elif content is not None:
|
elif content is not None:
|
||||||
add_msg = ' The raw error result: {content}'.format(content=to_text(content))
|
add_msg = ' The raw error result: {content}'.format(content=to_text(content))
|
||||||
msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=format_http_status(code))
|
msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=code_msg)
|
||||||
elif content_json is not None:
|
elif content_json is not None:
|
||||||
add_msg = ' The JSON result: {content}'.format(content=content_json)
|
add_msg = ' The JSON result: {content}'.format(content=content_json)
|
||||||
elif content is not None:
|
elif content is not None:
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class Order(object):
|
|||||||
self.identifiers = []
|
self.identifiers = []
|
||||||
for identifier in data['identifiers']:
|
for identifier in data['identifiers']:
|
||||||
self.identifiers.append((identifier['type'], identifier['value']))
|
self.identifiers.append((identifier['type'], identifier['value']))
|
||||||
|
self.replaces_cert_id = data.get('replaces')
|
||||||
self.finalize_uri = data.get('finalize')
|
self.finalize_uri = data.get('finalize')
|
||||||
self.certificate_uri = data.get('certificate')
|
self.certificate_uri = data.get('certificate')
|
||||||
self.authorization_uris = data['authorizations']
|
self.authorization_uris = data['authorizations']
|
||||||
@@ -44,6 +45,7 @@ class Order(object):
|
|||||||
|
|
||||||
self.status = None
|
self.status = None
|
||||||
self.identifiers = []
|
self.identifiers = []
|
||||||
|
self.replaces_cert_id = None
|
||||||
self.finalize_uri = None
|
self.finalize_uri = None
|
||||||
self.certificate_uri = None
|
self.certificate_uri = None
|
||||||
self.authorization_uris = []
|
self.authorization_uris = []
|
||||||
@@ -62,7 +64,7 @@ class Order(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, client, identifiers):
|
def create(cls, client, identifiers, replaces_cert_id=None):
|
||||||
'''
|
'''
|
||||||
Start a new certificate order (ACME v2 protocol).
|
Start a new certificate order (ACME v2 protocol).
|
||||||
https://tools.ietf.org/html/rfc8555#section-7.4
|
https://tools.ietf.org/html/rfc8555#section-7.4
|
||||||
@@ -76,6 +78,8 @@ class Order(object):
|
|||||||
new_order = {
|
new_order = {
|
||||||
"identifiers": acme_identifiers
|
"identifiers": acme_identifiers
|
||||||
}
|
}
|
||||||
|
if replaces_cert_id is not None:
|
||||||
|
new_order["replaces"] = replaces_cert_id
|
||||||
result, info = client.send_signed_request(
|
result, info = client.send_signed_request(
|
||||||
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
|
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
|
||||||
return cls.from_json(client, result, info['location'])
|
return cls.from_json(client, result, info['location'])
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ __metaclass__ = type
|
|||||||
|
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import datetime
|
||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
@@ -19,6 +20,10 @@ from ansible.module_utils.six.moves.urllib.parse import unquote
|
|||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import convert_int_to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import get_now_datetime
|
||||||
|
|
||||||
|
|
||||||
def nopad_b64(data):
|
def nopad_b64(data):
|
||||||
return base64.urlsafe_b64encode(data).decode('utf8').replace("=", "")
|
return base64.urlsafe_b64encode(data).decode('utf8').replace("=", "")
|
||||||
@@ -65,8 +70,61 @@ def pem_to_der(pem_filename=None, pem_content=None):
|
|||||||
def process_links(info, callback):
|
def process_links(info, callback):
|
||||||
'''
|
'''
|
||||||
Process link header, calls callback for every link header with the URL and relation as options.
|
Process link header, calls callback for every link header with the URL and relation as options.
|
||||||
|
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
|
||||||
'''
|
'''
|
||||||
if 'link' in info:
|
if 'link' in info:
|
||||||
link = info['link']
|
link = info['link']
|
||||||
for url, relation in re.findall(r'<([^>]+)>;\s*rel="(\w+)"', link):
|
for url, relation in re.findall(r'<([^>]+)>;\s*rel="(\w+)"', link):
|
||||||
callback(unquote(url), relation)
|
callback(unquote(url), relation)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_retry_after(value, relative_with_timezone=True, now=None):
|
||||||
|
'''
|
||||||
|
Parse the value of a Retry-After header and return a timestamp.
|
||||||
|
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||||
|
'''
|
||||||
|
# First try a number of seconds
|
||||||
|
try:
|
||||||
|
delta = datetime.timedelta(seconds=int(value))
|
||||||
|
if now is None:
|
||||||
|
now = get_now_datetime(relative_with_timezone)
|
||||||
|
return now + delta
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
return datetime.datetime.strptime(value, '%a, %d %b %Y %H:%M:%S GMT')
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise ValueError('Cannot parse Retry-After header value %s' % repr(value))
|
||||||
|
|
||||||
|
|
||||||
|
def compute_cert_id(
|
||||||
|
backend,
|
||||||
|
cert_info=None,
|
||||||
|
cert_filename=None,
|
||||||
|
cert_content=None,
|
||||||
|
none_if_required_information_is_missing=False,
|
||||||
|
):
|
||||||
|
# Obtain certificate info if not provided
|
||||||
|
if cert_info is None:
|
||||||
|
cert_info = backend.get_cert_information(cert_filename=cert_filename, cert_content=cert_content)
|
||||||
|
|
||||||
|
# Convert Authority Key Identifier to string
|
||||||
|
if cert_info.authority_key_identifier is None:
|
||||||
|
if none_if_required_information_is_missing:
|
||||||
|
return None
|
||||||
|
raise ModuleFailException('Certificate has no Authority Key Identifier extension')
|
||||||
|
aki = to_native(base64.urlsafe_b64encode(cert_info.authority_key_identifier)).replace('=', '')
|
||||||
|
|
||||||
|
# Convert serial number to string
|
||||||
|
serial_bytes = convert_int_to_bytes(cert_info.serial_number)
|
||||||
|
if ord(serial_bytes[:1]) >= 128:
|
||||||
|
serial_bytes = b'\x00' + serial_bytes
|
||||||
|
serial = to_native(base64.urlsafe_b64encode(serial_bytes)).replace('=', '')
|
||||||
|
|
||||||
|
# Compose cert ID
|
||||||
|
return '{aki}.{serial}'.format(aki=aki, serial=serial)
|
||||||
|
|||||||
75
plugins/module_utils/argspec.py
Normal file
75
plugins/module_utils/argspec.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_list(value):
|
||||||
|
if value is None:
|
||||||
|
return []
|
||||||
|
return list(value)
|
||||||
|
|
||||||
|
|
||||||
|
class ArgumentSpec:
|
||||||
|
def __init__(self, argument_spec=None, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None):
|
||||||
|
self.argument_spec = argument_spec or {}
|
||||||
|
self.mutually_exclusive = _ensure_list(mutually_exclusive)
|
||||||
|
self.required_together = _ensure_list(required_together)
|
||||||
|
self.required_one_of = _ensure_list(required_one_of)
|
||||||
|
self.required_if = _ensure_list(required_if)
|
||||||
|
self.required_by = required_by or {}
|
||||||
|
|
||||||
|
def update_argspec(self, **kwargs):
|
||||||
|
self.argument_spec.update(kwargs)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def update(self, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None):
|
||||||
|
if mutually_exclusive:
|
||||||
|
self.mutually_exclusive.extend(mutually_exclusive)
|
||||||
|
if required_together:
|
||||||
|
self.required_together.extend(required_together)
|
||||||
|
if required_one_of:
|
||||||
|
self.required_one_of.extend(required_one_of)
|
||||||
|
if required_if:
|
||||||
|
self.required_if.extend(required_if)
|
||||||
|
if required_by:
|
||||||
|
for k, v in required_by.items():
|
||||||
|
if k in self.required_by:
|
||||||
|
v = list(self.required_by[k]) + list(v)
|
||||||
|
self.required_by[k] = v
|
||||||
|
return self
|
||||||
|
|
||||||
|
def merge(self, other):
|
||||||
|
self.update_argspec(**other.argument_spec)
|
||||||
|
self.update(
|
||||||
|
mutually_exclusive=other.mutually_exclusive,
|
||||||
|
required_together=other.required_together,
|
||||||
|
required_one_of=other.required_one_of,
|
||||||
|
required_if=other.required_if,
|
||||||
|
required_by=other.required_by,
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def create_ansible_module_helper(self, clazz, args, **kwargs):
|
||||||
|
return clazz(
|
||||||
|
*args,
|
||||||
|
argument_spec=self.argument_spec,
|
||||||
|
mutually_exclusive=self.mutually_exclusive,
|
||||||
|
required_together=self.required_together,
|
||||||
|
required_one_of=self.required_one_of,
|
||||||
|
required_if=self.required_if,
|
||||||
|
required_by=self.required_by,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def create_ansible_module(self, **kwargs):
|
||||||
|
return self.create_ansible_module_helper(AnsibleModule, (), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ('ArgumentSpec', )
|
||||||
@@ -19,6 +19,7 @@ from .basic import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .cryptography_support import (
|
from .cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
cryptography_decode_name,
|
cryptography_decode_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +28,11 @@ from ._obj2txt import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: once cryptography has a _utc variant of InvalidityDate.invalidity_date, set this
|
||||||
|
# to True and adjust get_invalidity_date() accordingly.
|
||||||
|
# (https://github.com/pyca/cryptography/issues/10818)
|
||||||
|
CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE = False
|
||||||
|
|
||||||
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||||
|
|
||||||
|
|
||||||
@@ -55,7 +61,7 @@ else:
|
|||||||
def cryptography_decode_revoked_certificate(cert):
|
def cryptography_decode_revoked_certificate(cert):
|
||||||
result = {
|
result = {
|
||||||
'serial_number': cert.serial_number,
|
'serial_number': cert.serial_number,
|
||||||
'revocation_date': cert.revocation_date,
|
'revocation_date': get_revocation_date(cert),
|
||||||
'issuer': None,
|
'issuer': None,
|
||||||
'issuer_critical': False,
|
'issuer_critical': False,
|
||||||
'reason': None,
|
'reason': None,
|
||||||
@@ -77,7 +83,7 @@ def cryptography_decode_revoked_certificate(cert):
|
|||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
ext = cert.extensions.get_extension_for_class(x509.InvalidityDate)
|
ext = cert.extensions.get_extension_for_class(x509.InvalidityDate)
|
||||||
result['invalidity_date'] = ext.value.invalidity_date
|
result['invalidity_date'] = get_invalidity_date(ext.value)
|
||||||
result['invalidity_date_critical'] = ext.critical
|
result['invalidity_date_critical'] = ext.critical
|
||||||
except x509.ExtensionNotFound:
|
except x509.ExtensionNotFound:
|
||||||
pass
|
pass
|
||||||
@@ -112,3 +118,38 @@ def cryptography_get_signature_algorithm_oid_from_crl(crl):
|
|||||||
crl._x509_crl.sig_alg.algorithm
|
crl._x509_crl.sig_alg.algorithm
|
||||||
)
|
)
|
||||||
return x509.oid.ObjectIdentifier(dotted)
|
return x509.oid.ObjectIdentifier(dotted)
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_update(obj):
|
||||||
|
if CRYPTOGRAPHY_TIMEZONE:
|
||||||
|
return obj.next_update_utc
|
||||||
|
return obj.next_update
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_update(obj):
|
||||||
|
if CRYPTOGRAPHY_TIMEZONE:
|
||||||
|
return obj.last_update_utc
|
||||||
|
return obj.last_update
|
||||||
|
|
||||||
|
|
||||||
|
def get_revocation_date(obj):
|
||||||
|
if CRYPTOGRAPHY_TIMEZONE:
|
||||||
|
return obj.revocation_date_utc
|
||||||
|
return obj.revocation_date
|
||||||
|
|
||||||
|
|
||||||
|
def get_invalidity_date(obj):
|
||||||
|
# TODO: special handling if CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE is True
|
||||||
|
return obj.invalidity_date
|
||||||
|
|
||||||
|
|
||||||
|
def set_next_update(builder, value):
|
||||||
|
return builder.next_update(value)
|
||||||
|
|
||||||
|
|
||||||
|
def set_last_update(builder, value):
|
||||||
|
return builder.last_update(value)
|
||||||
|
|
||||||
|
|
||||||
|
def set_revocation_date(builder, value):
|
||||||
|
return builder.revocation_date(value)
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ try:
|
|||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
_HAS_CRYPTOGRAPHY = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
_HAS_CRYPTOGRAPHY = False
|
||||||
# Error handled in the calling module.
|
# Error handled in the calling module.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -106,6 +108,11 @@ from ._objects import (
|
|||||||
from ._obj2txt import obj2txt
|
from ._obj2txt import obj2txt
|
||||||
|
|
||||||
|
|
||||||
|
CRYPTOGRAPHY_TIMEZONE = False
|
||||||
|
if _HAS_CRYPTOGRAPHY:
|
||||||
|
CRYPTOGRAPHY_TIMEZONE = LooseVersion(cryptography.__version__) >= LooseVersion('42.0.0')
|
||||||
|
|
||||||
|
|
||||||
DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
|
DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +121,7 @@ def cryptography_get_extensions_from_cert(cert):
|
|||||||
try:
|
try:
|
||||||
# Since cryptography will not 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
|
# (that is only stored for unrecognized extensions), we have to re-do
|
||||||
# the extension parsing outselves.
|
# the extension parsing ourselves.
|
||||||
backend = default_backend()
|
backend = default_backend()
|
||||||
try:
|
try:
|
||||||
# For certain old versions of cryptography, backend is a MultiBackend object,
|
# For certain old versions of cryptography, backend is a MultiBackend object,
|
||||||
@@ -166,7 +173,7 @@ def cryptography_get_extensions_from_csr(csr):
|
|||||||
try:
|
try:
|
||||||
# Since cryptography will not 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
|
# (that is only stored for unrecognized extensions), we have to re-do
|
||||||
# the extension parsing outselves.
|
# the extension parsing ourselves.
|
||||||
backend = default_backend()
|
backend = default_backend()
|
||||||
try:
|
try:
|
||||||
# For certain old versions of cryptography, backend is a MultiBackend object,
|
# For certain old versions of cryptography, backend is a MultiBackend object,
|
||||||
@@ -807,3 +814,23 @@ def cryptography_verify_certificate_signature(certificate, signer_public_key):
|
|||||||
certificate.signature_hash_algorithm,
|
certificate.signature_hash_algorithm,
|
||||||
signer_public_key
|
signer_public_key
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_not_valid_after(obj):
|
||||||
|
if CRYPTOGRAPHY_TIMEZONE:
|
||||||
|
return obj.not_valid_after_utc
|
||||||
|
return obj.not_valid_after
|
||||||
|
|
||||||
|
|
||||||
|
def get_not_valid_before(obj):
|
||||||
|
if CRYPTOGRAPHY_TIMEZONE:
|
||||||
|
return obj.not_valid_before_utc
|
||||||
|
return obj.not_valid_before
|
||||||
|
|
||||||
|
|
||||||
|
def set_not_valid_after(builder, value):
|
||||||
|
return builder.not_valid_after(value)
|
||||||
|
|
||||||
|
|
||||||
|
def set_not_valid_before(builder, value):
|
||||||
|
return builder.not_valid_before(value)
|
||||||
|
|||||||
@@ -42,9 +42,18 @@ def quick_is_not_prime(n):
|
|||||||
that we could not detect quickly whether it is not prime.
|
that we could not detect quickly whether it is not prime.
|
||||||
'''
|
'''
|
||||||
if n <= 2:
|
if n <= 2:
|
||||||
return True
|
return n < 2
|
||||||
# The constant in the next line is the product of all primes < 200
|
# The constant in the next line is the product of all primes < 200
|
||||||
if simple_gcd(n, 7799922041683461553249199106329813876687996789903550945093032474868511536164700810) > 1:
|
prime_product = 7799922041683461553249199106329813876687996789903550945093032474868511536164700810
|
||||||
|
gcd = simple_gcd(n, prime_product)
|
||||||
|
if gcd > 1:
|
||||||
|
if n < 200 and gcd == n:
|
||||||
|
# Explicitly check for all primes < 200
|
||||||
|
return n not in (
|
||||||
|
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
|
||||||
|
89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179,
|
||||||
|
181, 191, 193, 197, 199,
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
# TODO: maybe do some iterations of Miller-Rabin to increase confidence
|
# TODO: maybe do some iterations of Miller-Rabin to increase confidence
|
||||||
# (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test)
|
# (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test)
|
||||||
@@ -54,17 +63,111 @@ def quick_is_not_prime(n):
|
|||||||
python_version = (sys.version_info[0], sys.version_info[1])
|
python_version = (sys.version_info[0], sys.version_info[1])
|
||||||
if python_version >= (2, 7) or python_version >= (3, 1):
|
if python_version >= (2, 7) or python_version >= (3, 1):
|
||||||
# Ansible still supports Python 2.6 on remote nodes
|
# Ansible still supports Python 2.6 on remote nodes
|
||||||
|
|
||||||
|
def count_bytes(no):
|
||||||
|
"""
|
||||||
|
Given an integer, compute the number of bytes necessary to store its absolute value.
|
||||||
|
"""
|
||||||
|
no = abs(no)
|
||||||
|
if no == 0:
|
||||||
|
return 0
|
||||||
|
return (no.bit_length() + 7) // 8
|
||||||
|
|
||||||
def count_bits(no):
|
def count_bits(no):
|
||||||
|
"""
|
||||||
|
Given an integer, compute the number of bits necessary to store its absolute value.
|
||||||
|
"""
|
||||||
no = abs(no)
|
no = abs(no)
|
||||||
if no == 0:
|
if no == 0:
|
||||||
return 0
|
return 0
|
||||||
return no.bit_length()
|
return no.bit_length()
|
||||||
else:
|
else:
|
||||||
# Slow, but works
|
# Slow, but works
|
||||||
|
def count_bytes(no):
|
||||||
|
"""
|
||||||
|
Given an integer, compute the number of bytes necessary to store its absolute value.
|
||||||
|
"""
|
||||||
|
no = abs(no)
|
||||||
|
count = 0
|
||||||
|
while no > 0:
|
||||||
|
no >>= 8
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
def count_bits(no):
|
def count_bits(no):
|
||||||
|
"""
|
||||||
|
Given an integer, compute the number of bits necessary to store its absolute value.
|
||||||
|
"""
|
||||||
no = abs(no)
|
no = abs(no)
|
||||||
count = 0
|
count = 0
|
||||||
while no > 0:
|
while no > 0:
|
||||||
no >>= 1
|
no >>= 1
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
# Python 3 (and newer)
|
||||||
|
def _convert_int_to_bytes(count, no):
|
||||||
|
return no.to_bytes(count, byteorder='big')
|
||||||
|
|
||||||
|
def _convert_bytes_to_int(data):
|
||||||
|
return int.from_bytes(data, byteorder='big', signed=False)
|
||||||
|
|
||||||
|
def _to_hex(no):
|
||||||
|
return hex(no)[2:]
|
||||||
|
else:
|
||||||
|
# Python 2
|
||||||
|
def _convert_int_to_bytes(count, n):
|
||||||
|
if n == 0 and count == 0:
|
||||||
|
return ''
|
||||||
|
h = '%x' % n
|
||||||
|
if len(h) > 2 * count:
|
||||||
|
raise Exception('Number {1} needs more than {0} bytes!'.format(count, n))
|
||||||
|
return ('0' * (2 * count - len(h)) + h).decode('hex')
|
||||||
|
|
||||||
|
def _convert_bytes_to_int(data):
|
||||||
|
v = 0
|
||||||
|
for x in data:
|
||||||
|
v = (v << 8) | ord(x)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def _to_hex(no):
|
||||||
|
return '%x' % no
|
||||||
|
|
||||||
|
|
||||||
|
def convert_int_to_bytes(no, count=None):
|
||||||
|
"""
|
||||||
|
Convert the absolute value of an integer to a byte string in network byte order.
|
||||||
|
|
||||||
|
If ``count`` is provided, it must be sufficiently large so that the integer's
|
||||||
|
absolute value can be represented with these number of bytes. The resulting byte
|
||||||
|
string will have length exactly ``count``.
|
||||||
|
|
||||||
|
The value zero will be converted to an empty byte string if ``count`` is provided.
|
||||||
|
"""
|
||||||
|
no = abs(no)
|
||||||
|
if count is None:
|
||||||
|
count = count_bytes(no)
|
||||||
|
return _convert_int_to_bytes(count, no)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_int_to_hex(no, digits=None):
|
||||||
|
"""
|
||||||
|
Convert the absolute value of an integer to a string of hexadecimal digits.
|
||||||
|
|
||||||
|
If ``digits`` is provided, the string will be padded on the left with ``0``s so
|
||||||
|
that the returned value has length ``digits``. If ``digits`` is not sufficient,
|
||||||
|
the string will be longer.
|
||||||
|
"""
|
||||||
|
no = abs(no)
|
||||||
|
value = _to_hex(no)
|
||||||
|
if digits is not None and len(value) < digits:
|
||||||
|
value = '0' * (digits - len(value)) + value
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def convert_bytes_to_int(data):
|
||||||
|
"""
|
||||||
|
Convert a byte string to an unsigned integer in network byte order.
|
||||||
|
"""
|
||||||
|
return _convert_bytes_to_int(data)
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import traceback
|
|||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
OpenSSLObjectError,
|
OpenSSLObjectError,
|
||||||
@@ -32,6 +32,8 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
|||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
cryptography_compare_public_keys,
|
cryptography_compare_public_keys,
|
||||||
|
get_not_valid_after,
|
||||||
|
get_not_valid_before,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import (
|
||||||
@@ -251,12 +253,12 @@ class CertificateBackend(object):
|
|||||||
|
|
||||||
# Check not before
|
# Check not before
|
||||||
if not_before is not None and not self.ignore_timestamps:
|
if not_before is not None and not self.ignore_timestamps:
|
||||||
if self.existing_certificate.not_valid_before != not_before:
|
if get_not_valid_before(self.existing_certificate) != not_before:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check not after
|
# Check not after
|
||||||
if not_after is not None and not self.ignore_timestamps:
|
if not_after is not None and not self.ignore_timestamps:
|
||||||
if self.existing_certificate.not_valid_after != not_after:
|
if get_not_valid_after(self.existing_certificate) != not_after:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ __metaclass__ = type
|
|||||||
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
||||||
@@ -19,11 +18,12 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import EC
|
|||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||||
load_certificate,
|
load_certificate,
|
||||||
get_relative_time_option,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
cryptography_serial_number_of_cert,
|
cryptography_serial_number_of_cert,
|
||||||
|
get_not_valid_after,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
||||||
@@ -32,6 +32,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
|||||||
CertificateProvider,
|
CertificateProvider,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
get_now_datetime,
|
||||||
|
get_relative_time_option,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cryptography.x509.oid import NameOID
|
from cryptography.x509.oid import NameOID
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -42,7 +47,12 @@ class EntrustCertificateBackend(CertificateBackend):
|
|||||||
def __init__(self, module, backend):
|
def __init__(self, module, backend):
|
||||||
super(EntrustCertificateBackend, self).__init__(module, backend)
|
super(EntrustCertificateBackend, self).__init__(module, backend)
|
||||||
self.trackingId = None
|
self.trackingId = None
|
||||||
self.notAfter = get_relative_time_option(module.params['entrust_not_after'], 'entrust_not_after', backend=self.backend)
|
self.notAfter = get_relative_time_option(
|
||||||
|
module.params['entrust_not_after'],
|
||||||
|
'entrust_not_after',
|
||||||
|
backend=self.backend,
|
||||||
|
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||||
|
)
|
||||||
|
|
||||||
if self.csr_content is None and self.csr_path is None:
|
if self.csr_content is None and self.csr_path is None:
|
||||||
raise CertificateError(
|
raise CertificateError(
|
||||||
@@ -99,7 +109,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
|||||||
# Handle expiration (30 days if not specified)
|
# Handle expiration (30 days if not specified)
|
||||||
expiry = self.notAfter
|
expiry = self.notAfter
|
||||||
if not expiry:
|
if not expiry:
|
||||||
gmt_now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime()))
|
gmt_now = get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
expiry = gmt_now + datetime.timedelta(days=365)
|
expiry = gmt_now + datetime.timedelta(days=365)
|
||||||
|
|
||||||
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
|
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
|
||||||
@@ -154,7 +164,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
|||||||
expiry = None
|
expiry = None
|
||||||
if self.backend == 'cryptography':
|
if self.backend == 'cryptography':
|
||||||
serial_number = "{0:X}".format(cryptography_serial_number_of_cert(self.existing_certificate))
|
serial_number = "{0:X}".format(cryptography_serial_number_of_cert(self.existing_certificate))
|
||||||
expiry = self.existing_certificate.not_valid_after
|
expiry = get_not_valid_after(self.existing_certificate)
|
||||||
|
|
||||||
# get some information about the expiry of this certificate
|
# get some information about the expiry of this certificate
|
||||||
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
|
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import binascii
|
import binascii
|
||||||
import datetime
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
@@ -27,16 +26,23 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
|||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
cryptography_decode_name,
|
cryptography_decode_name,
|
||||||
cryptography_get_extensions_from_cert,
|
cryptography_get_extensions_from_cert,
|
||||||
cryptography_oid_to_name,
|
cryptography_oid_to_name,
|
||||||
cryptography_serial_number_of_cert,
|
cryptography_serial_number_of_cert,
|
||||||
|
get_not_valid_after,
|
||||||
|
get_not_valid_before,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||||
get_publickey_info,
|
get_publickey_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
get_now_datetime,
|
||||||
|
)
|
||||||
|
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||||
|
|
||||||
CRYPTOGRAPHY_IMP_ERR = None
|
CRYPTOGRAPHY_IMP_ERR = None
|
||||||
@@ -143,9 +149,9 @@ class CertificateInfoRetrieval(object):
|
|||||||
def _get_issuer_uri(self):
|
def _get_issuer_uri(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_info(self, prefer_one_fingerprint=False):
|
def get_info(self, prefer_one_fingerprint=False, der_support_enabled=False):
|
||||||
result = dict()
|
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()
|
result['signature_algorithm'] = self._get_signature_algorithm()
|
||||||
subject = self._get_subject_ordered()
|
subject = self._get_subject_ordered()
|
||||||
@@ -169,7 +175,7 @@ class CertificateInfoRetrieval(object):
|
|||||||
not_after = self.get_not_after()
|
not_after = self.get_not_after()
|
||||||
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
|
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
|
||||||
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
|
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
|
||||||
result['expired'] = not_after < datetime.datetime.utcnow()
|
result['expired'] = not_after < get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
|
|
||||||
result['public_key'] = to_native(self._get_public_key_pem())
|
result['public_key'] = to_native(self._get_public_key_pem())
|
||||||
|
|
||||||
@@ -322,10 +328,10 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
|||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
def get_not_before(self):
|
def get_not_before(self):
|
||||||
return self.cert.not_valid_before
|
return get_not_valid_before(self.cert)
|
||||||
|
|
||||||
def get_not_after(self):
|
def get_not_after(self):
|
||||||
return self.cert.not_valid_after
|
return get_not_valid_after(self.cert)
|
||||||
|
|
||||||
def _get_public_key_pem(self):
|
def _get_public_key_pem(self):
|
||||||
return self.cert.public_key().public_bytes(
|
return self.cert.public_key().public_bytes(
|
||||||
|
|||||||
@@ -22,15 +22,19 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
|
|||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||||
load_privatekey,
|
load_privatekey,
|
||||||
load_certificate,
|
load_certificate,
|
||||||
get_relative_time_option,
|
|
||||||
select_message_digest,
|
select_message_digest,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
cryptography_compare_public_keys,
|
cryptography_compare_public_keys,
|
||||||
cryptography_key_needs_digest_for_signing,
|
cryptography_key_needs_digest_for_signing,
|
||||||
cryptography_serial_number_of_cert,
|
cryptography_serial_number_of_cert,
|
||||||
cryptography_verify_certificate_signature,
|
cryptography_verify_certificate_signature,
|
||||||
|
get_not_valid_after,
|
||||||
|
get_not_valid_before,
|
||||||
|
set_not_valid_after,
|
||||||
|
set_not_valid_before,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
||||||
@@ -40,6 +44,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
|||||||
CertificateProvider,
|
CertificateProvider,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
get_relative_time_option,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
@@ -55,8 +63,18 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
|
|
||||||
self.create_subject_key_identifier = module.params['ownca_create_subject_key_identifier']
|
self.create_subject_key_identifier = module.params['ownca_create_subject_key_identifier']
|
||||||
self.create_authority_key_identifier = module.params['ownca_create_authority_key_identifier']
|
self.create_authority_key_identifier = module.params['ownca_create_authority_key_identifier']
|
||||||
self.notBefore = get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before', backend=self.backend)
|
self.notBefore = get_relative_time_option(
|
||||||
self.notAfter = get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after', backend=self.backend)
|
module.params['ownca_not_before'],
|
||||||
|
'ownca_not_before',
|
||||||
|
backend=self.backend,
|
||||||
|
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||||
|
)
|
||||||
|
self.notAfter = get_relative_time_option(
|
||||||
|
module.params['ownca_not_after'],
|
||||||
|
'ownca_not_after',
|
||||||
|
backend=self.backend,
|
||||||
|
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||||
|
)
|
||||||
self.digest = select_message_digest(module.params['ownca_digest'])
|
self.digest = select_message_digest(module.params['ownca_digest'])
|
||||||
self.version = module.params['ownca_version']
|
self.version = module.params['ownca_version']
|
||||||
self.serial_number = x509.random_serial_number()
|
self.serial_number = x509.random_serial_number()
|
||||||
@@ -120,8 +138,8 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
cert_builder = cert_builder.subject_name(self.csr.subject)
|
cert_builder = cert_builder.subject_name(self.csr.subject)
|
||||||
cert_builder = cert_builder.issuer_name(self.ca_cert.subject)
|
cert_builder = cert_builder.issuer_name(self.ca_cert.subject)
|
||||||
cert_builder = cert_builder.serial_number(self.serial_number)
|
cert_builder = cert_builder.serial_number(self.serial_number)
|
||||||
cert_builder = cert_builder.not_valid_before(self.notBefore)
|
cert_builder = set_not_valid_before(cert_builder, self.notBefore)
|
||||||
cert_builder = cert_builder.not_valid_after(self.notAfter)
|
cert_builder = set_not_valid_after(cert_builder, self.notAfter)
|
||||||
cert_builder = cert_builder.public_key(self.csr.public_key())
|
cert_builder = cert_builder.public_key(self.csr.public_key())
|
||||||
has_ski = False
|
has_ski = False
|
||||||
for extension in self.csr.extensions:
|
for extension in self.csr.extensions:
|
||||||
@@ -220,8 +238,8 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
if self.cert is None:
|
if self.cert is None:
|
||||||
self.cert = self.existing_certificate
|
self.cert = self.existing_certificate
|
||||||
result.update({
|
result.update({
|
||||||
'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"),
|
'notBefore': get_not_valid_before(self.cert).strftime("%Y%m%d%H%M%SZ"),
|
||||||
'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"),
|
'notAfter': get_not_valid_after(self.cert).strftime("%Y%m%d%H%M%SZ"),
|
||||||
'serial_number': cryptography_serial_number_of_cert(self.cert),
|
'serial_number': cryptography_serial_number_of_cert(self.cert),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,18 @@ import os
|
|||||||
from random import randrange
|
from random import randrange
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||||
get_relative_time_option,
|
|
||||||
select_message_digest,
|
select_message_digest,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
cryptography_key_needs_digest_for_signing,
|
cryptography_key_needs_digest_for_signing,
|
||||||
cryptography_serial_number_of_cert,
|
cryptography_serial_number_of_cert,
|
||||||
cryptography_verify_certificate_signature,
|
cryptography_verify_certificate_signature,
|
||||||
|
get_not_valid_after,
|
||||||
|
get_not_valid_before,
|
||||||
|
set_not_valid_after,
|
||||||
|
set_not_valid_before,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
||||||
@@ -30,6 +34,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
|||||||
CertificateProvider,
|
CertificateProvider,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
get_relative_time_option,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
@@ -44,8 +52,18 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
super(SelfSignedCertificateBackendCryptography, self).__init__(module, 'cryptography')
|
super(SelfSignedCertificateBackendCryptography, self).__init__(module, 'cryptography')
|
||||||
|
|
||||||
self.create_subject_key_identifier = module.params['selfsigned_create_subject_key_identifier']
|
self.create_subject_key_identifier = module.params['selfsigned_create_subject_key_identifier']
|
||||||
self.notBefore = get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before', backend=self.backend)
|
self.notBefore = get_relative_time_option(
|
||||||
self.notAfter = get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after', backend=self.backend)
|
module.params['selfsigned_not_before'],
|
||||||
|
'selfsigned_not_before',
|
||||||
|
backend=self.backend,
|
||||||
|
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||||
|
)
|
||||||
|
self.notAfter = get_relative_time_option(
|
||||||
|
module.params['selfsigned_not_after'],
|
||||||
|
'selfsigned_not_after',
|
||||||
|
backend=self.backend,
|
||||||
|
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||||
|
)
|
||||||
self.digest = select_message_digest(module.params['selfsigned_digest'])
|
self.digest = select_message_digest(module.params['selfsigned_digest'])
|
||||||
self.version = module.params['selfsigned_version']
|
self.version = module.params['selfsigned_version']
|
||||||
self.serial_number = x509.random_serial_number()
|
self.serial_number = x509.random_serial_number()
|
||||||
@@ -95,8 +113,8 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
cert_builder = cert_builder.subject_name(self.csr.subject)
|
cert_builder = cert_builder.subject_name(self.csr.subject)
|
||||||
cert_builder = cert_builder.issuer_name(self.csr.subject)
|
cert_builder = cert_builder.issuer_name(self.csr.subject)
|
||||||
cert_builder = cert_builder.serial_number(self.serial_number)
|
cert_builder = cert_builder.serial_number(self.serial_number)
|
||||||
cert_builder = cert_builder.not_valid_before(self.notBefore)
|
cert_builder = set_not_valid_before(cert_builder, self.notBefore)
|
||||||
cert_builder = cert_builder.not_valid_after(self.notAfter)
|
cert_builder = set_not_valid_after(cert_builder, self.notAfter)
|
||||||
cert_builder = cert_builder.public_key(self.privatekey.public_key())
|
cert_builder = cert_builder.public_key(self.privatekey.public_key())
|
||||||
has_ski = False
|
has_ski = False
|
||||||
for extension in self.csr.extensions:
|
for extension in self.csr.extensions:
|
||||||
@@ -154,8 +172,8 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
if self.cert is None:
|
if self.cert is None:
|
||||||
self.cert = self.existing_certificate
|
self.cert = self.existing_certificate
|
||||||
result.update({
|
result.update({
|
||||||
'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"),
|
'notBefore': get_not_valid_before(self.cert).strftime("%Y%m%d%H%M%SZ"),
|
||||||
'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"),
|
'notAfter': get_not_valid_after(self.cert).strftime("%Y%m%d%H%M%SZ"),
|
||||||
'serial_number': cryptography_serial_number_of_cert(self.cert),
|
'serial_number': cryptography_serial_number_of_cert(self.cert),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -10,26 +10,19 @@ __metaclass__ = type
|
|||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec as _ArgumentSpec
|
||||||
|
|
||||||
class ArgumentSpec:
|
|
||||||
def __init__(self, argument_spec, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None):
|
|
||||||
self.argument_spec = argument_spec
|
|
||||||
self.mutually_exclusive = mutually_exclusive or []
|
|
||||||
self.required_together = required_together or []
|
|
||||||
self.required_one_of = required_one_of or []
|
|
||||||
self.required_if = required_if or []
|
|
||||||
self.required_by = required_by or {}
|
|
||||||
|
|
||||||
|
class ArgumentSpec(_ArgumentSpec):
|
||||||
def create_ansible_module_helper(self, clazz, args, **kwargs):
|
def create_ansible_module_helper(self, clazz, args, **kwargs):
|
||||||
return clazz(
|
result = super(ArgumentSpec, self).create_ansible_module_helper(clazz, args, **kwargs)
|
||||||
*args,
|
result.deprecate(
|
||||||
argument_spec=self.argument_spec,
|
"The crypto.module_backends.common module utils is deprecated and will be removed from community.crypto 3.0.0."
|
||||||
mutually_exclusive=self.mutually_exclusive,
|
" Use the argspec module utils from community.crypto instead.",
|
||||||
required_together=self.required_together,
|
version='3.0.0',
|
||||||
required_one_of=self.required_one_of,
|
collection_name='community.crypto',
|
||||||
required_if=self.required_if,
|
)
|
||||||
required_by=self.required_by,
|
return result
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
def create_ansible_module(self, **kwargs):
|
|
||||||
return self.create_ansible_module_helper(AnsibleModule, (), **kwargs)
|
__all__ = ('AnsibleModule', 'ArgumentSpec')
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from ansible.module_utils import six
|
|||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
@@ -49,8 +51,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
|||||||
get_csr_info,
|
get_csr_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
|
||||||
|
|
||||||
|
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from ansible.module_utils import six
|
|||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
@@ -42,8 +44,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
|||||||
get_privatekey_info,
|
get_privatekey_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
|
||||||
|
|
||||||
|
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ from ansible.module_utils import six
|
|||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||||
load_file,
|
load_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
CRYPTOGRAPHY_HAS_X25519,
|
CRYPTOGRAPHY_HAS_X25519,
|
||||||
CRYPTOGRAPHY_HAS_X448,
|
CRYPTOGRAPHY_HAS_X448,
|
||||||
@@ -37,8 +39,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
|
|||||||
identify_private_key_format,
|
identify_private_key_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
|
||||||
|
|
||||||
|
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ class PrivateKeyConvertBackend:
|
|||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _load_private_key(self, data, passphrase, current_hint=None):
|
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)."""
|
"""Check whether data can be loaded as a private key with the provided passphrase. Return tuple (type, private_key)."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def needs_conversion(self):
|
def needs_conversion(self):
|
||||||
|
|||||||
@@ -105,9 +105,12 @@ def _check_dsa_consistency(key_public_data, key_private_data):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _is_cryptography_key_consistent(key, key_public_data, key_private_data):
|
def _is_cryptography_key_consistent(key, key_public_data, key_private_data, warn_func=None):
|
||||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||||
return bool(key._backend._lib.RSA_check_key(key._rsa_cdata))
|
# key._backend was removed in cryptography 42.0.0
|
||||||
|
backend = getattr(key, '_backend', None)
|
||||||
|
if backend is not None:
|
||||||
|
return bool(backend._lib.RSA_check_key(key._rsa_cdata))
|
||||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
if isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||||
result = _check_dsa_consistency(key_public_data, key_private_data)
|
result = _check_dsa_consistency(key_public_data, key_private_data)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
@@ -157,6 +160,8 @@ def _is_cryptography_key_consistent(key, key_public_data, key_private_data):
|
|||||||
except cryptography.exceptions.InvalidSignature:
|
except cryptography.exceptions.InvalidSignature:
|
||||||
return False
|
return False
|
||||||
# For X25519 and X448, there's no test yet.
|
# For X25519 and X448, there's no test yet.
|
||||||
|
if warn_func is not None:
|
||||||
|
warn_func('Cannot determine consistency for key of type %s' % type(key))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -253,7 +258,7 @@ class PrivateKeyInfoRetrievalCryptography(PrivateKeyInfoRetrieval):
|
|||||||
return _get_cryptography_private_key_info(self.key, need_private_key_data=need_private_key_data)
|
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):
|
def _is_key_consistent(self, key_public_data, key_private_data):
|
||||||
return _is_cryptography_key_consistent(self.key, key_public_data, key_private_data)
|
return _is_cryptography_key_consistent(self.key, key_public_data, key_private_data, warn_func=self.module.warn)
|
||||||
|
|
||||||
|
|
||||||
def get_privatekey_info(module, backend, content, passphrase=None, return_private_key_data=False, prefer_one_fingerprint=False):
|
def get_privatekey_info(module, backend, content, passphrase=None, return_private_key_data=False, prefer_one_fingerprint=False):
|
||||||
|
|||||||
@@ -9,15 +9,19 @@ __metaclass__ = type
|
|||||||
|
|
||||||
|
|
||||||
PEM_START = '-----BEGIN '
|
PEM_START = '-----BEGIN '
|
||||||
|
PEM_END_START = '-----END '
|
||||||
PEM_END = '-----'
|
PEM_END = '-----'
|
||||||
PKCS8_PRIVATEKEY_NAMES = ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY')
|
PKCS8_PRIVATEKEY_NAMES = ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY')
|
||||||
PKCS1_PRIVATEKEY_SUFFIX = ' PRIVATE KEY'
|
PKCS1_PRIVATEKEY_SUFFIX = ' PRIVATE KEY'
|
||||||
|
|
||||||
|
|
||||||
def identify_pem_format(content):
|
def identify_pem_format(content, encoding='utf-8'):
|
||||||
'''Given the contents of a binary file, tests whether this could be a PEM file.'''
|
'''Given the contents of a binary file, tests whether this could be a PEM file.'''
|
||||||
try:
|
try:
|
||||||
lines = content.decode('utf-8').splitlines(False)
|
first_pem = extract_first_pem(content.decode(encoding))
|
||||||
|
if first_pem is None:
|
||||||
|
return False
|
||||||
|
lines = first_pem.splitlines(False)
|
||||||
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
|
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
|
||||||
return True
|
return True
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
@@ -25,14 +29,17 @@ def identify_pem_format(content):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def identify_private_key_format(content):
|
def identify_private_key_format(content, encoding='utf-8'):
|
||||||
'''Given the contents of a private key file, identifies its format.'''
|
'''Given the contents of a private key file, identifies its format.'''
|
||||||
# See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85
|
# See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85
|
||||||
# (PEM_read_bio_PrivateKey)
|
# (PEM_read_bio_PrivateKey)
|
||||||
# and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47
|
# and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47
|
||||||
# (PEM_STRING_PKCS8, PEM_STRING_PKCS8INF)
|
# (PEM_STRING_PKCS8, PEM_STRING_PKCS8INF)
|
||||||
try:
|
try:
|
||||||
lines = content.decode('utf-8').splitlines(False)
|
first_pem = extract_first_pem(content.decode(encoding))
|
||||||
|
if first_pem is None:
|
||||||
|
return 'raw'
|
||||||
|
lines = first_pem.splitlines(False)
|
||||||
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
|
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
|
||||||
name = lines[0][len(PEM_START):-len(PEM_END)]
|
name = lines[0][len(PEM_START):-len(PEM_END)]
|
||||||
if name in PKCS8_PRIVATEKEY_NAMES:
|
if name in PKCS8_PRIVATEKEY_NAMES:
|
||||||
@@ -71,3 +78,31 @@ def extract_first_pem(text):
|
|||||||
if not all_pems:
|
if not all_pems:
|
||||||
return None
|
return None
|
||||||
return all_pems[0]
|
return all_pems[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_type(line, start=PEM_START):
|
||||||
|
if not line.startswith(start):
|
||||||
|
return None
|
||||||
|
if not line.endswith(PEM_END):
|
||||||
|
return None
|
||||||
|
return line[len(start):-len(PEM_END)]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_pem(content, strict=False):
|
||||||
|
lines = content.splitlines()
|
||||||
|
if len(lines) < 3:
|
||||||
|
raise ValueError('PEM must have at least 3 lines, have only {count}'.format(count=len(lines)))
|
||||||
|
header_type = _extract_type(lines[0])
|
||||||
|
if header_type is None:
|
||||||
|
raise ValueError('First line is not of format {start}...{end}: {line!r}'.format(start=PEM_START, end=PEM_END, line=lines[0]))
|
||||||
|
footer_type = _extract_type(lines[-1], start=PEM_END_START)
|
||||||
|
if strict:
|
||||||
|
if header_type != footer_type:
|
||||||
|
raise ValueError('Header type ({header}) is different from footer type ({footer})'.format(header=header_type, footer=footer_type))
|
||||||
|
for idx, line in enumerate(lines[1:-2]):
|
||||||
|
if len(line) != 64:
|
||||||
|
raise ValueError('Line {idx} has length {len} instead of 64'.format(idx=idx, len=len(line)))
|
||||||
|
if not (0 < len(lines[-2]) <= 64):
|
||||||
|
raise ValueError('Last line has length {len}, should be in (0, 64]'.format(len=len(lines[-2])))
|
||||||
|
content = lines[1:-1]
|
||||||
|
return header_type, ''.join(content)
|
||||||
|
|||||||
@@ -9,14 +9,24 @@ __metaclass__ = type
|
|||||||
|
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import datetime
|
|
||||||
import errno
|
import errno
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
|
identify_pem_format,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import ( # noqa: F401, pylint: disable=unused-import
|
||||||
|
# These imports are for backwards compatibility
|
||||||
|
get_now_datetime,
|
||||||
|
ensure_utc_timezone,
|
||||||
|
convert_relative_to_datetime,
|
||||||
|
get_relative_time_option,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
@@ -189,7 +199,7 @@ def load_publickey(path=None, content=None, backend=None):
|
|||||||
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
|
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
|
||||||
|
|
||||||
|
|
||||||
def load_certificate(path, content=None, backend='cryptography'):
|
def load_certificate(path, content=None, backend='cryptography', der_support_enabled=False):
|
||||||
"""Load the specified certificate."""
|
"""Load the specified certificate."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -201,12 +211,21 @@ def load_certificate(path, content=None, backend='cryptography'):
|
|||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc)
|
||||||
if backend == 'pyopenssl':
|
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':
|
elif backend == 'cryptography':
|
||||||
try:
|
if der_support_enabled is False or identify_pem_format(cert_content):
|
||||||
return x509.load_pem_x509_certificate(cert_content, cryptography_backend())
|
try:
|
||||||
except ValueError as exc:
|
return x509.load_pem_x509_certificate(cert_content, cryptography_backend())
|
||||||
raise OpenSSLObjectError(exc)
|
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='cryptography'):
|
def load_certificate_request(path, content=None, backend='cryptography'):
|
||||||
@@ -266,69 +285,6 @@ def parse_ordered_name_field(input_list, name_field_name):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def convert_relative_to_datetime(relative_time_string):
|
|
||||||
"""Get a datetime.datetime or None from a string in the time format described in sshd_config(5)"""
|
|
||||||
|
|
||||||
parsed_result = re.match(
|
|
||||||
r"^(?P<prefix>[+-])((?P<weeks>\d+)[wW])?((?P<days>\d+)[dD])?((?P<hours>\d+)[hH])?((?P<minutes>\d+)[mM])?((?P<seconds>\d+)[sS]?)?$",
|
|
||||||
relative_time_string)
|
|
||||||
|
|
||||||
if parsed_result is None or len(relative_time_string) == 1:
|
|
||||||
# not matched or only a single "+" or "-"
|
|
||||||
return None
|
|
||||||
|
|
||||||
offset = datetime.timedelta(0)
|
|
||||||
if parsed_result.group("weeks") is not None:
|
|
||||||
offset += datetime.timedelta(weeks=int(parsed_result.group("weeks")))
|
|
||||||
if parsed_result.group("days") is not None:
|
|
||||||
offset += datetime.timedelta(days=int(parsed_result.group("days")))
|
|
||||||
if parsed_result.group("hours") is not None:
|
|
||||||
offset += datetime.timedelta(hours=int(parsed_result.group("hours")))
|
|
||||||
if parsed_result.group("minutes") is not None:
|
|
||||||
offset += datetime.timedelta(
|
|
||||||
minutes=int(parsed_result.group("minutes")))
|
|
||||||
if parsed_result.group("seconds") is not None:
|
|
||||||
offset += datetime.timedelta(
|
|
||||||
seconds=int(parsed_result.group("seconds")))
|
|
||||||
|
|
||||||
if parsed_result.group("prefix") == "+":
|
|
||||||
return datetime.datetime.utcnow() + offset
|
|
||||||
else:
|
|
||||||
return datetime.datetime.utcnow() - offset
|
|
||||||
|
|
||||||
|
|
||||||
def get_relative_time_option(input_string, input_name, backend='cryptography'):
|
|
||||||
"""Return an absolute timespec if a relative timespec or an ASN1 formatted
|
|
||||||
string is provided.
|
|
||||||
|
|
||||||
The return value will be a datetime object for the cryptography backend,
|
|
||||||
and a ASN1 formatted string for the pyopenssl backend."""
|
|
||||||
result = to_native(input_string)
|
|
||||||
if result is None:
|
|
||||||
raise OpenSSLObjectError(
|
|
||||||
'The timespec "%s" for %s is not valid' %
|
|
||||||
input_string, input_name)
|
|
||||||
# Relative time
|
|
||||||
if result.startswith("+") or result.startswith("-"):
|
|
||||||
result_datetime = convert_relative_to_datetime(result)
|
|
||||||
if backend == 'pyopenssl':
|
|
||||||
return result_datetime.strftime("%Y%m%d%H%M%SZ")
|
|
||||||
elif backend == 'cryptography':
|
|
||||||
return result_datetime
|
|
||||||
# Absolute time
|
|
||||||
if backend == 'cryptography':
|
|
||||||
for date_fmt in ['%Y%m%d%H%M%SZ', '%Y%m%d%H%MZ', '%Y%m%d%H%M%S%z', '%Y%m%d%H%M%z']:
|
|
||||||
try:
|
|
||||||
return datetime.datetime.strptime(result, date_fmt)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
raise OpenSSLObjectError(
|
|
||||||
'The time spec "%s" for %s is invalid' %
|
|
||||||
(input_string, input_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def select_message_digest(digest_string):
|
def select_message_digest(digest_string):
|
||||||
digest = None
|
digest = None
|
||||||
if digest_string == 'sha256':
|
if digest_string == 'sha256':
|
||||||
|
|||||||
64
plugins/module_utils/gnupg/cli.py
Normal file
64
plugins/module_utils/gnupg/cli.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2023, 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 os
|
||||||
|
|
||||||
|
from ansible.module_utils import six
|
||||||
|
|
||||||
|
|
||||||
|
class GPGError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class GPGRunner(object):
|
||||||
|
@abc.abstractmethod
|
||||||
|
def run_command(self, command, check_rc=True, data=None):
|
||||||
|
"""
|
||||||
|
Run ``[gpg] + command`` and return ``(rc, stdout, stderr)``.
|
||||||
|
|
||||||
|
If ``data`` is not ``None``, it will be provided as stdin.
|
||||||
|
The code assumes it is a bytes string.
|
||||||
|
|
||||||
|
Returned stdout and stderr are native Python strings.
|
||||||
|
Pass ``check_rc=False`` to allow return codes != 0.
|
||||||
|
|
||||||
|
Raises a ``GPGError`` in case of errors.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_fingerprint_from_stdout(stdout):
|
||||||
|
lines = stdout.splitlines(False)
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('fpr:'):
|
||||||
|
parts = line.split(':')
|
||||||
|
if len(parts) <= 9 or not parts[9]:
|
||||||
|
raise GPGError('Result line "{line}" does not have fingerprint as 10th component'.format(line=line))
|
||||||
|
return parts[9]
|
||||||
|
raise GPGError('Cannot extract fingerprint from stdout "{stdout}"'.format(stdout=stdout))
|
||||||
|
|
||||||
|
|
||||||
|
def get_fingerprint_from_file(gpg_runner, path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise GPGError('{path} does not exist'.format(path=path))
|
||||||
|
stdout = gpg_runner.run_command(
|
||||||
|
['--no-keyring', '--with-colons', '--import-options', 'show-only', '--import', path],
|
||||||
|
check_rc=True,
|
||||||
|
)[1]
|
||||||
|
return get_fingerprint_from_stdout(stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def get_fingerprint_from_bytes(gpg_runner, content):
|
||||||
|
stdout = gpg_runner.run_command(
|
||||||
|
['--no-keyring', '--with-colons', '--import-options', 'show-only', '--import', '/dev/stdin'],
|
||||||
|
data=content,
|
||||||
|
check_rc=True,
|
||||||
|
)[1]
|
||||||
|
return get_fingerprint_from_stdout(stdout)
|
||||||
@@ -127,7 +127,7 @@ class OpensshModule(object):
|
|||||||
ssh_bin = self.module.get_bin_path('ssh')
|
ssh_bin = self.module.get_bin_path('ssh')
|
||||||
if not ssh_bin:
|
if not ssh_bin:
|
||||||
return ""
|
return ""
|
||||||
return parse_openssh_version(self.module.run_command([ssh_bin, '-V', '-q'])[2].strip())
|
return parse_openssh_version(self.module.run_command([ssh_bin, '-V', '-q'], check_rc=True)[2].strip())
|
||||||
|
|
||||||
@_restore_all_on_failure
|
@_restore_all_on_failure
|
||||||
def _safe_secure_move(self, sources_and_destinations):
|
def _safe_secure_move(self, sources_and_destinations):
|
||||||
@@ -208,14 +208,18 @@ class KeygenCommand(object):
|
|||||||
def get_private_key(self, private_key_path, **kwargs):
|
def get_private_key(self, private_key_path, **kwargs):
|
||||||
return self._run_command([self._bin_path, '-l', '-f', private_key_path], **kwargs)
|
return self._run_command([self._bin_path, '-l', '-f', private_key_path], **kwargs)
|
||||||
|
|
||||||
def update_comment(self, private_key_path, comment, **kwargs):
|
def update_comment(self, private_key_path, comment, force_new_format=True, **kwargs):
|
||||||
if os.path.exists(private_key_path) and not os.access(private_key_path, os.W_OK):
|
if os.path.exists(private_key_path) and not os.access(private_key_path, os.W_OK):
|
||||||
try:
|
try:
|
||||||
os.chmod(private_key_path, stat.S_IWUSR + stat.S_IRUSR)
|
os.chmod(private_key_path, stat.S_IWUSR + stat.S_IRUSR)
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
raise e("The private key at %s is not writeable preventing a comment update" % private_key_path)
|
raise e("The private key at %s is not writeable preventing a comment update" % private_key_path)
|
||||||
|
|
||||||
return self._run_command([self._bin_path, '-q', '-o', '-c', '-C', comment, '-f', private_key_path], **kwargs)
|
command = [self._bin_path, '-q']
|
||||||
|
if force_new_format:
|
||||||
|
command.append('-o')
|
||||||
|
command.extend(['-c', '-C', comment, '-f', private_key_path])
|
||||||
|
return self._run_command(command, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class PrivateKey(object):
|
class PrivateKey(object):
|
||||||
|
|||||||
@@ -323,23 +323,27 @@ class KeypairBackendOpensshBin(KeypairBackend):
|
|||||||
self.ssh_keygen = KeygenCommand(self.module)
|
self.ssh_keygen = KeygenCommand(self.module)
|
||||||
|
|
||||||
def _generate_keypair(self, private_key_path):
|
def _generate_keypair(self, private_key_path):
|
||||||
self.ssh_keygen.generate_keypair(private_key_path, self.size, self.type, self.comment)
|
self.ssh_keygen.generate_keypair(private_key_path, self.size, self.type, self.comment, check_rc=True)
|
||||||
|
|
||||||
def _get_private_key(self):
|
def _get_private_key(self):
|
||||||
private_key_content = self.ssh_keygen.get_private_key(self.private_key_path)[1]
|
rc, private_key_content, err = self.ssh_keygen.get_private_key(self.private_key_path, check_rc=False)
|
||||||
|
if rc != 0:
|
||||||
|
raise ValueError(err)
|
||||||
return PrivateKey.from_string(private_key_content)
|
return PrivateKey.from_string(private_key_content)
|
||||||
|
|
||||||
def _get_public_key(self):
|
def _get_public_key(self):
|
||||||
public_key_content = self.ssh_keygen.get_matching_public_key(self.private_key_path)[1]
|
public_key_content = self.ssh_keygen.get_matching_public_key(self.private_key_path, check_rc=True)[1]
|
||||||
return PublicKey.from_string(public_key_content)
|
return PublicKey.from_string(public_key_content)
|
||||||
|
|
||||||
def _private_key_readable(self):
|
def _private_key_readable(self):
|
||||||
rc, stdout, stderr = self.ssh_keygen.get_matching_public_key(self.private_key_path)
|
rc, stdout, stderr = self.ssh_keygen.get_matching_public_key(self.private_key_path, check_rc=False)
|
||||||
return not (rc == 255 or any_in(stderr, 'is not a public key file', 'incorrect passphrase', 'load failed'))
|
return not (rc == 255 or any_in(stderr, 'is not a public key file', 'incorrect passphrase', 'load failed'))
|
||||||
|
|
||||||
def _update_comment(self):
|
def _update_comment(self):
|
||||||
try:
|
try:
|
||||||
self.ssh_keygen.update_comment(self.private_key_path, self.comment)
|
ssh_version = self._get_ssh_version() or "7.8"
|
||||||
|
force_new_format = LooseVersion('6.5') <= LooseVersion(ssh_version) < LooseVersion('7.8')
|
||||||
|
self.ssh_keygen.update_comment(self.private_key_path, self.comment, force_new_format=force_new_format, check_rc=True)
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
self.module.fail_json(msg=to_native(e))
|
self.module.fail_json(msg=to_native(e))
|
||||||
|
|
||||||
|
|||||||
@@ -22,18 +22,24 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import binascii
|
import binascii
|
||||||
|
import datetime as _datetime
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.common.text.converters import to_text
|
from ansible.module_utils.common.text.converters import to_text
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import convert_relative_to_datetime
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import (
|
from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import (
|
||||||
OpensshParser,
|
OpensshParser,
|
||||||
_OpensshWriter,
|
_OpensshWriter,
|
||||||
)
|
)
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
add_or_remove_timezone as _add_or_remove_timezone,
|
||||||
|
convert_relative_to_datetime,
|
||||||
|
UTC as _UTC,
|
||||||
|
)
|
||||||
|
|
||||||
# See https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
|
# See https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
|
||||||
_USER_TYPE = 1
|
_USER_TYPE = 1
|
||||||
@@ -61,8 +67,11 @@ _ECDSA_CURVE_IDENTIFIERS_LOOKUP = {
|
|||||||
b'nistp521': 'ecdsa-nistp521',
|
b'nistp521': 'ecdsa-nistp521',
|
||||||
}
|
}
|
||||||
|
|
||||||
_ALWAYS = datetime(1970, 1, 1)
|
_USE_TIMEZONE = sys.version_info >= (3, 6)
|
||||||
_FOREVER = datetime.max
|
|
||||||
|
|
||||||
|
_ALWAYS = _add_or_remove_timezone(datetime(1970, 1, 1), with_timezone=_USE_TIMEZONE)
|
||||||
|
_FOREVER = datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC) if _USE_TIMEZONE else datetime.max
|
||||||
|
|
||||||
_CRITICAL_OPTIONS = (
|
_CRITICAL_OPTIONS = (
|
||||||
'force-command',
|
'force-command',
|
||||||
@@ -136,7 +145,7 @@ class OpensshCertificateTimeParameters(object):
|
|||||||
elif dt == _FOREVER:
|
elif dt == _FOREVER:
|
||||||
result = 'forever'
|
result = 'forever'
|
||||||
else:
|
else:
|
||||||
result = dt.isoformat() if date_format == 'human_readable' else dt.strftime("%Y%m%d%H%M%S")
|
result = dt.isoformat().replace('+00:00', '') if date_format == 'human_readable' else dt.strftime("%Y%m%d%H%M%S")
|
||||||
elif date_format == 'timestamp':
|
elif date_format == 'timestamp':
|
||||||
td = dt - _ALWAYS
|
td = dt - _ALWAYS
|
||||||
result = int((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6)
|
result = int((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6)
|
||||||
@@ -167,7 +176,10 @@ class OpensshCertificateTimeParameters(object):
|
|||||||
result = _FOREVER
|
result = _FOREVER
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
result = datetime.utcfromtimestamp(timestamp)
|
if _USE_TIMEZONE:
|
||||||
|
result = datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc)
|
||||||
|
else:
|
||||||
|
result = datetime.utcfromtimestamp(timestamp)
|
||||||
except OverflowError as e:
|
except OverflowError as e:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
return result
|
return result
|
||||||
@@ -180,11 +192,11 @@ class OpensshCertificateTimeParameters(object):
|
|||||||
elif time_string == 'forever':
|
elif time_string == 'forever':
|
||||||
result = _FOREVER
|
result = _FOREVER
|
||||||
elif is_relative_time_string(time_string):
|
elif is_relative_time_string(time_string):
|
||||||
result = convert_relative_to_datetime(time_string)
|
result = convert_relative_to_datetime(time_string, with_timezone=_USE_TIMEZONE)
|
||||||
else:
|
else:
|
||||||
for time_format in ("%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"):
|
for time_format in ("%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"):
|
||||||
try:
|
try:
|
||||||
result = datetime.strptime(time_string, time_format)
|
result = _add_or_remove_timezone(datetime.strptime(time_string, time_format), with_timezone=_USE_TIMEZONE)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
if result is None:
|
if result is None:
|
||||||
|
|||||||
56
plugins/module_utils/serial.py
Normal file
56
plugins/module_utils/serial.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024, 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_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
|
||||||
|
convert_int_to_hex,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def th(number):
|
||||||
|
abs_number = abs(number)
|
||||||
|
mod_10 = abs_number % 10
|
||||||
|
mod_100 = abs_number % 100
|
||||||
|
if mod_100 not in (11, 12, 13):
|
||||||
|
if mod_10 == 1:
|
||||||
|
return 'st'
|
||||||
|
if mod_10 == 2:
|
||||||
|
return 'nd'
|
||||||
|
if mod_10 == 3:
|
||||||
|
return 'rd'
|
||||||
|
return 'th'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_serial(value):
|
||||||
|
"""
|
||||||
|
Given a colon-separated string of hexadecimal byte values, converts it to an integer.
|
||||||
|
"""
|
||||||
|
value = to_native(value)
|
||||||
|
result = 0
|
||||||
|
for i, part in enumerate(value.split(':')):
|
||||||
|
try:
|
||||||
|
part_value = int(part, 16)
|
||||||
|
if part_value < 0 or part_value > 255:
|
||||||
|
raise ValueError('the value is not in range [0, 255]')
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError("The {idx}{th} part {part!r} is not a hexadecimal number in range [0, 255]: {exc}".format(
|
||||||
|
idx=i + 1, th=th(i + 1), part=part, exc=exc))
|
||||||
|
result = (result << 8) | part_value
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def to_serial(value):
|
||||||
|
"""
|
||||||
|
Given an integer, converts its absolute value to a colon-separated string of hexadecimal byte values.
|
||||||
|
"""
|
||||||
|
value = convert_int_to_hex(value).upper()
|
||||||
|
if len(value) % 2 != 0:
|
||||||
|
value = '0' + value
|
||||||
|
return ':'.join(value[i:i + 2] for i in range(0, len(value), 2))
|
||||||
171
plugins/module_utils/time.py
Normal file
171
plugins/module_utils/time.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024, 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 datetime
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
|
OpenSSLObjectError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
UTC = datetime.timezone.utc
|
||||||
|
except AttributeError:
|
||||||
|
_DURATION_ZERO = datetime.timedelta(0)
|
||||||
|
|
||||||
|
class _UTCClass(datetime.tzinfo):
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return _DURATION_ZERO
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return _DURATION_ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return 'UTC'
|
||||||
|
|
||||||
|
def fromutc(self, dt):
|
||||||
|
return dt
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'UTC'
|
||||||
|
|
||||||
|
UTC = _UTCClass()
|
||||||
|
|
||||||
|
|
||||||
|
def get_now_datetime(with_timezone):
|
||||||
|
if with_timezone:
|
||||||
|
return datetime.datetime.now(tz=UTC)
|
||||||
|
return datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_utc_timezone(timestamp):
|
||||||
|
if timestamp.tzinfo is UTC:
|
||||||
|
return timestamp
|
||||||
|
if timestamp.tzinfo is None:
|
||||||
|
# We assume that naive datetime objects use timezone UTC!
|
||||||
|
return timestamp.replace(tzinfo=UTC)
|
||||||
|
return timestamp.astimezone(UTC)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_timezone(timestamp):
|
||||||
|
# Convert to native datetime object
|
||||||
|
if timestamp.tzinfo is None:
|
||||||
|
return timestamp
|
||||||
|
if timestamp.tzinfo is not UTC:
|
||||||
|
timestamp = timestamp.astimezone(UTC)
|
||||||
|
return timestamp.replace(tzinfo=None)
|
||||||
|
|
||||||
|
|
||||||
|
def add_or_remove_timezone(timestamp, with_timezone):
|
||||||
|
return ensure_utc_timezone(timestamp) if with_timezone else remove_timezone(timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 3):
|
||||||
|
def get_epoch_seconds(timestamp):
|
||||||
|
epoch = datetime.datetime(1970, 1, 1, tzinfo=UTC if timestamp.tzinfo is not None else None)
|
||||||
|
delta = timestamp - epoch
|
||||||
|
try:
|
||||||
|
return delta.total_seconds()
|
||||||
|
except AttributeError:
|
||||||
|
# Python 2.6 and earlier: total_seconds() does not yet exist, so we use the formula from
|
||||||
|
# https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds
|
||||||
|
return (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6
|
||||||
|
else:
|
||||||
|
def get_epoch_seconds(timestamp):
|
||||||
|
return timestamp.timestamp()
|
||||||
|
|
||||||
|
|
||||||
|
def from_epoch_seconds(timestamp, with_timezone):
|
||||||
|
if with_timezone:
|
||||||
|
return datetime.datetime.fromtimestamp(timestamp, UTC)
|
||||||
|
return datetime.datetime.utcfromtimestamp(timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_relative_to_datetime(relative_time_string, with_timezone=False, now=None):
|
||||||
|
"""Get a datetime.datetime or None from a string in the time format described in sshd_config(5)"""
|
||||||
|
|
||||||
|
parsed_result = re.match(
|
||||||
|
r"^(?P<prefix>[+-])((?P<weeks>\d+)[wW])?((?P<days>\d+)[dD])?((?P<hours>\d+)[hH])?((?P<minutes>\d+)[mM])?((?P<seconds>\d+)[sS]?)?$",
|
||||||
|
relative_time_string)
|
||||||
|
|
||||||
|
if parsed_result is None or len(relative_time_string) == 1:
|
||||||
|
# not matched or only a single "+" or "-"
|
||||||
|
return None
|
||||||
|
|
||||||
|
offset = datetime.timedelta(0)
|
||||||
|
if parsed_result.group("weeks") is not None:
|
||||||
|
offset += datetime.timedelta(weeks=int(parsed_result.group("weeks")))
|
||||||
|
if parsed_result.group("days") is not None:
|
||||||
|
offset += datetime.timedelta(days=int(parsed_result.group("days")))
|
||||||
|
if parsed_result.group("hours") is not None:
|
||||||
|
offset += datetime.timedelta(hours=int(parsed_result.group("hours")))
|
||||||
|
if parsed_result.group("minutes") is not None:
|
||||||
|
offset += datetime.timedelta(
|
||||||
|
minutes=int(parsed_result.group("minutes")))
|
||||||
|
if parsed_result.group("seconds") is not None:
|
||||||
|
offset += datetime.timedelta(
|
||||||
|
seconds=int(parsed_result.group("seconds")))
|
||||||
|
|
||||||
|
if now is None:
|
||||||
|
now = get_now_datetime(with_timezone=with_timezone)
|
||||||
|
else:
|
||||||
|
now = add_or_remove_timezone(now, with_timezone=with_timezone)
|
||||||
|
|
||||||
|
if parsed_result.group("prefix") == "+":
|
||||||
|
return now + offset
|
||||||
|
else:
|
||||||
|
return now - offset
|
||||||
|
|
||||||
|
|
||||||
|
def get_relative_time_option(input_string, input_name, backend='cryptography', with_timezone=False, now=None):
|
||||||
|
"""Return an absolute timespec if a relative timespec or an ASN1 formatted
|
||||||
|
string is provided.
|
||||||
|
|
||||||
|
The return value will be a datetime object for the cryptography backend,
|
||||||
|
and a ASN1 formatted string for the pyopenssl backend."""
|
||||||
|
result = to_native(input_string)
|
||||||
|
if result is None:
|
||||||
|
raise OpenSSLObjectError(
|
||||||
|
'The timespec "%s" for %s is not valid' %
|
||||||
|
input_string, input_name)
|
||||||
|
# Relative time
|
||||||
|
if result.startswith("+") or result.startswith("-"):
|
||||||
|
result_datetime = convert_relative_to_datetime(result, with_timezone=with_timezone, now=now)
|
||||||
|
if backend == 'pyopenssl':
|
||||||
|
return result_datetime.strftime("%Y%m%d%H%M%SZ")
|
||||||
|
elif backend == 'cryptography':
|
||||||
|
return result_datetime
|
||||||
|
# Absolute time
|
||||||
|
if backend == 'pyopenssl':
|
||||||
|
return input_string
|
||||||
|
elif backend == 'cryptography':
|
||||||
|
for date_fmt, length in [
|
||||||
|
('%Y%m%d%H%M%SZ', 15), # this also parses '202401020304Z', but as datetime(2024, 1, 2, 3, 0, 4)
|
||||||
|
('%Y%m%d%H%MZ', 13),
|
||||||
|
('%Y%m%d%H%M%S%z', 14 + 5), # this also parses '202401020304+0000', but as datetime(2024, 1, 2, 3, 0, 4, tzinfo=...)
|
||||||
|
('%Y%m%d%H%M%z', 12 + 5),
|
||||||
|
]:
|
||||||
|
if len(result) != length:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
res = datetime.datetime.strptime(result, date_fmt)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return add_or_remove_timezone(res, with_timezone=with_timezone)
|
||||||
|
|
||||||
|
raise OpenSSLObjectError(
|
||||||
|
'The time spec "%s" for %s is invalid' %
|
||||||
|
(input_string, input_name)
|
||||||
|
)
|
||||||
@@ -22,7 +22,7 @@ description:
|
|||||||
notes:
|
notes:
|
||||||
- "The M(community.crypto.acme_certificate) module also allows to do basic account management.
|
- "The M(community.crypto.acme_certificate) module also allows to do basic account management.
|
||||||
When using both modules, it is recommended to disable account management
|
When using both modules, it is recommended to disable account management
|
||||||
for M(community.crypto.acme_certificate). For that, use the C(modify_account) option of
|
for M(community.crypto.acme_certificate). For that, use the O(community.crypto.acme_certificate#module:modify_account) option of
|
||||||
M(community.crypto.acme_certificate)."
|
M(community.crypto.acme_certificate)."
|
||||||
seealso:
|
seealso:
|
||||||
- name: Automatic Certificate Management Environment (ACME)
|
- name: Automatic Certificate Management Environment (ACME)
|
||||||
@@ -37,7 +37,8 @@ seealso:
|
|||||||
- module: community.crypto.acme_inspect
|
- module: community.crypto.acme_inspect
|
||||||
description: Allows to debug problems.
|
description: Allows to debug problems.
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.acme
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.account
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
- community.crypto.attributes.actiongroup_acme
|
- community.crypto.attributes.actiongroup_acme
|
||||||
attributes:
|
attributes:
|
||||||
@@ -49,9 +50,9 @@ options:
|
|||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- "The state of the account, to be identified by its account key."
|
- "The state of the account, to be identified by its account key."
|
||||||
- "If the state is C(absent), the account will either not exist or be
|
- "If the state is V(absent), the account will either not exist or be
|
||||||
deactivated."
|
deactivated."
|
||||||
- "If the state is C(changed_key), the account must exist. The account
|
- "If the state is V(changed_key), the account must exist. The account
|
||||||
key will be changed; no other information will be touched."
|
key will be changed; no other information will be touched."
|
||||||
type: str
|
type: str
|
||||||
required: true
|
required: true
|
||||||
@@ -61,7 +62,7 @@ options:
|
|||||||
- changed_key
|
- changed_key
|
||||||
allow_creation:
|
allow_creation:
|
||||||
description:
|
description:
|
||||||
- "Whether account creation is allowed (when state is C(present))."
|
- "Whether account creation is allowed (when state is V(present))."
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
contact:
|
contact:
|
||||||
@@ -70,30 +71,30 @@ options:
|
|||||||
- "Email addresses must be prefixed with C(mailto:)."
|
- "Email addresses must be prefixed with C(mailto:)."
|
||||||
- "See U(https://tools.ietf.org/html/rfc8555#section-7.3)
|
- "See U(https://tools.ietf.org/html/rfc8555#section-7.3)
|
||||||
for what is allowed."
|
for what is allowed."
|
||||||
- "Must be specified when state is C(present). Will be ignored
|
- "Must be specified when state is V(present). Will be ignored
|
||||||
if state is C(absent) or C(changed_key)."
|
if state is V(absent) or V(changed_key)."
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
default: []
|
default: []
|
||||||
terms_agreed:
|
terms_agreed:
|
||||||
description:
|
description:
|
||||||
- "Boolean indicating whether you agree to the terms of service document."
|
- "Boolean indicating whether you agree to the terms of service document."
|
||||||
- "ACME servers can require this to be true."
|
- "ACME servers can require this to be V(true)."
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
new_account_key_src:
|
new_account_key_src:
|
||||||
description:
|
description:
|
||||||
- "Path to a file containing the ACME account RSA or Elliptic Curve key to change to."
|
- "Path to a file containing the ACME account RSA or Elliptic Curve key to change to."
|
||||||
- "Same restrictions apply as to C(account_key_src)."
|
- "Same restrictions apply as to O(account_key_src)."
|
||||||
- "Mutually exclusive with C(new_account_key_content)."
|
- "Mutually exclusive with O(new_account_key_content)."
|
||||||
- "Required if C(new_account_key_content) is not used and state is C(changed_key)."
|
- "Required if O(new_account_key_content) is not used and O(state) is V(changed_key)."
|
||||||
type: path
|
type: path
|
||||||
new_account_key_content:
|
new_account_key_content:
|
||||||
description:
|
description:
|
||||||
- "Content of the ACME account RSA or Elliptic Curve key to change to."
|
- "Content of the ACME account RSA or Elliptic Curve key to change to."
|
||||||
- "Same restrictions apply as to C(account_key_content)."
|
- "Same restrictions apply as to O(account_key_content)."
|
||||||
- "Mutually exclusive with C(new_account_key_src)."
|
- "Mutually exclusive with O(new_account_key_src)."
|
||||||
- "Required if C(new_account_key_src) is not used and state is C(changed_key)."
|
- "Required if O(new_account_key_src) is not used and O(state) is V(changed_key)."
|
||||||
type: str
|
type: str
|
||||||
new_account_key_passphrase:
|
new_account_key_passphrase:
|
||||||
description:
|
description:
|
||||||
@@ -117,14 +118,14 @@ options:
|
|||||||
alg:
|
alg:
|
||||||
description:
|
description:
|
||||||
- The MAC algorithm provided by the CA.
|
- The MAC algorithm provided by the CA.
|
||||||
- If not specified by the CA, this is probably C(HS256).
|
- If not specified by the CA, this is probably V(HS256).
|
||||||
type: str
|
type: str
|
||||||
required: true
|
required: true
|
||||||
choices: [ HS256, HS384, HS512 ]
|
choices: [ HS256, HS384, HS512 ]
|
||||||
key:
|
key:
|
||||||
description:
|
description:
|
||||||
- Base64 URL encoded value of the MAC key provided by the CA.
|
- Base64 URL encoded value of the MAC key provided by the CA.
|
||||||
- Padding (C(=) symbols at the end) can be omitted.
|
- Padding (V(=) symbols at the end) can be omitted.
|
||||||
type: str
|
type: str
|
||||||
required: true
|
required: true
|
||||||
version_added: 1.1.0
|
version_added: 1.1.0
|
||||||
@@ -169,11 +170,9 @@ account_uri:
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
create_backend,
|
create_backend,
|
||||||
get_default_argspec,
|
create_default_argspec,
|
||||||
ACMEClient,
|
ACMEClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -188,8 +187,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = get_default_argspec()
|
argument_spec = create_default_argspec()
|
||||||
argument_spec.update(dict(
|
argument_spec.update_argspec(
|
||||||
terms_agreed=dict(type='bool', default=False),
|
terms_agreed=dict(type='bool', default=False),
|
||||||
state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']),
|
state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']),
|
||||||
allow_creation=dict(type='bool', default=True),
|
allow_creation=dict(type='bool', default=True),
|
||||||
@@ -202,14 +201,9 @@ def main():
|
|||||||
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
|
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
|
||||||
key=dict(type='str', required=True, no_log=True),
|
key=dict(type='str', required=True, no_log=True),
|
||||||
))
|
))
|
||||||
))
|
)
|
||||||
module = AnsibleModule(
|
argument_spec.update(
|
||||||
argument_spec=argument_spec,
|
|
||||||
required_one_of=(
|
|
||||||
['account_key_src', 'account_key_content'],
|
|
||||||
),
|
|
||||||
mutually_exclusive=(
|
mutually_exclusive=(
|
||||||
['account_key_src', 'account_key_content'],
|
|
||||||
['new_account_key_src', 'new_account_key_content'],
|
['new_account_key_src', 'new_account_key_content'],
|
||||||
),
|
),
|
||||||
required_if=(
|
required_if=(
|
||||||
@@ -217,8 +211,8 @@ def main():
|
|||||||
# new_account_key_src and new_account_key_content are specified
|
# new_account_key_src and new_account_key_content are specified
|
||||||
['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True],
|
['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True],
|
||||||
),
|
),
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
)
|
||||||
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
backend = create_backend(module, True)
|
backend = create_backend(module, True)
|
||||||
|
|
||||||
if module.params['external_account_binding']:
|
if module.params['external_account_binding']:
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ notes:
|
|||||||
- "This module was called C(acme_account_facts) before Ansible 2.8. The usage
|
- "This module was called C(acme_account_facts) before Ansible 2.8. The usage
|
||||||
did not change."
|
did not change."
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.acme
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.account
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
- community.crypto.attributes.actiongroup_acme
|
- community.crypto.attributes.actiongroup_acme
|
||||||
- community.crypto.attributes.info_module
|
- community.crypto.attributes.info_module
|
||||||
@@ -34,11 +35,11 @@ options:
|
|||||||
description:
|
description:
|
||||||
- "Whether to retrieve the list of order URLs or order objects, if provided
|
- "Whether to retrieve the list of order URLs or order objects, if provided
|
||||||
by the ACME server."
|
by the ACME server."
|
||||||
- "A value of C(ignore) will not fetch the list of orders."
|
- "A value of V(ignore) will not fetch the list of orders."
|
||||||
- "If the value is not C(ignore) and the ACME server supports orders, the C(order_uris)
|
- "If the value is not V(ignore) and the ACME server supports orders, the RV(order_uris)
|
||||||
return value is always populated. The C(orders) return value is only returned
|
return value is always populated. The RV(orders) return value is only returned
|
||||||
if this option is set to C(object_list)."
|
if this option is set to V(object_list)."
|
||||||
- "Currently, Let's Encrypt does not return orders, so the C(orders) result
|
- "Currently, Let's Encrypt does not return orders, so the RV(orders) result
|
||||||
will always be empty."
|
will always be empty."
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
@@ -58,7 +59,7 @@ EXAMPLES = '''
|
|||||||
account_key_src: /etc/pki/cert/private/account.key
|
account_key_src: /etc/pki/cert/private/account.key
|
||||||
register: account_data
|
register: account_data
|
||||||
- name: Verify that account exists
|
- name: Verify that account exists
|
||||||
assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- account_data.exists
|
- account_data.exists
|
||||||
- name: Print account URI
|
- name: Print account URI
|
||||||
@@ -74,7 +75,7 @@ EXAMPLES = '''
|
|||||||
account_uri: "{{ acme_account_uri }}"
|
account_uri: "{{ acme_account_uri }}"
|
||||||
register: account_data
|
register: account_data
|
||||||
- name: Verify that account exists
|
- name: Verify that account exists
|
||||||
assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- account_data.exists
|
- account_data.exists
|
||||||
- name: Print account contacts
|
- name: Print account contacts
|
||||||
@@ -113,7 +114,7 @@ account:
|
|||||||
orders:
|
orders:
|
||||||
description:
|
description:
|
||||||
- A URL where a list of orders can be retrieved for this account.
|
- A URL where a list of orders can be retrieved for this account.
|
||||||
- Use the I(retrieve_orders) option to query this URL and retrieve the
|
- Use the O(retrieve_orders) option to query this URL and retrieve the
|
||||||
complete list of orders.
|
complete list of orders.
|
||||||
returned: always
|
returned: always
|
||||||
type: str
|
type: str
|
||||||
@@ -129,7 +130,7 @@ orders:
|
|||||||
- "The list of orders."
|
- "The list of orders."
|
||||||
type: list
|
type: list
|
||||||
elements: dict
|
elements: dict
|
||||||
returned: if account exists, I(retrieve_orders) is C(object_list), and server supports order listing
|
returned: if account exists, O(retrieve_orders) is V(object_list), and server supports order listing
|
||||||
contains:
|
contains:
|
||||||
status:
|
status:
|
||||||
description: The order's status.
|
description: The order's status.
|
||||||
@@ -144,7 +145,7 @@ orders:
|
|||||||
description:
|
description:
|
||||||
- When the order expires.
|
- When the order expires.
|
||||||
- Timestamp should be formatted as described in RFC3339.
|
- Timestamp should be formatted as described in RFC3339.
|
||||||
- Only required to be included in result when I(status) is C(pending) or C(valid).
|
- Only required to be included in result when RV(orders[].status) is V(pending) or V(valid).
|
||||||
type: str
|
type: str
|
||||||
returned: when server gives expiry date
|
returned: when server gives expiry date
|
||||||
identifiers:
|
identifiers:
|
||||||
@@ -154,14 +155,17 @@ orders:
|
|||||||
elements: dict
|
elements: dict
|
||||||
contains:
|
contains:
|
||||||
type:
|
type:
|
||||||
description: Type of identifier. C(dns) or C(ip).
|
description: Type of identifier.
|
||||||
type: str
|
type: str
|
||||||
|
choices:
|
||||||
|
- dns
|
||||||
|
- ip
|
||||||
value:
|
value:
|
||||||
description: Name of identifier. Hostname or IP address.
|
description: Name of identifier. Hostname or IP address.
|
||||||
type: str
|
type: str
|
||||||
wildcard:
|
wildcard:
|
||||||
description: "Whether I(value) is actually a wildcard. The wildcard
|
description: "Whether RV(orders[].identifiers[].value) is actually a wildcard. The wildcard
|
||||||
prefix C(*.) is not included in I(value) if this is C(true)."
|
prefix C(*.) is not included in RV(orders[].identifiers[].value) if this is V(true)."
|
||||||
type: bool
|
type: bool
|
||||||
returned: required to be included if the identifier is wildcarded
|
returned: required to be included if the identifier is wildcarded
|
||||||
notBefore:
|
notBefore:
|
||||||
@@ -202,19 +206,17 @@ orders:
|
|||||||
order_uris:
|
order_uris:
|
||||||
description:
|
description:
|
||||||
- "The list of orders."
|
- "The list of orders."
|
||||||
- "If I(retrieve_orders) is C(url_list), this will be a list of URLs."
|
- "If O(retrieve_orders) is V(url_list), this will be a list of URLs."
|
||||||
- "If I(retrieve_orders) is C(object_list), this will be a list of objects."
|
- "If O(retrieve_orders) is V(object_list), this will be a list of objects."
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
returned: if account exists, I(retrieve_orders) is not C(ignore), and server supports order listing
|
returned: if account exists, O(retrieve_orders) is not V(ignore), and server supports order listing
|
||||||
version_added: 1.5.0
|
version_added: 1.5.0
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
create_backend,
|
create_backend,
|
||||||
get_default_argspec,
|
create_default_argspec,
|
||||||
ACMEClient,
|
ACMEClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -267,20 +269,11 @@ def get_order(client, order_url):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = get_default_argspec()
|
argument_spec = create_default_argspec()
|
||||||
argument_spec.update(dict(
|
argument_spec.update_argspec(
|
||||||
retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']),
|
retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']),
|
||||||
))
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=argument_spec,
|
|
||||||
required_one_of=(
|
|
||||||
['account_key_src', 'account_key_content'],
|
|
||||||
),
|
|
||||||
mutually_exclusive=(
|
|
||||||
['account_key_src', 'account_key_content'],
|
|
||||||
),
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
)
|
||||||
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
backend = create_backend(module, True)
|
backend = create_backend(module, True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
142
plugins/modules/acme_ari_info.py
Normal file
142
plugins/modules/acme_ari_info.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2018 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 = '''
|
||||||
|
---
|
||||||
|
module: acme_ari_info
|
||||||
|
author: "Felix Fontein (@felixfontein)"
|
||||||
|
version_added: 2.20.0
|
||||||
|
short_description: Retrieves ACME Renewal Information (ARI) for a certificate
|
||||||
|
description:
|
||||||
|
- "Allows to retrieve renewal information on a certificate obtained with the
|
||||||
|
L(ACME protocol,https://tools.ietf.org/html/rfc8555)."
|
||||||
|
- "This module only works with the ACME v2 protocol, and requires the ACME server
|
||||||
|
to support the ARI extension (U(https://datatracker.ietf.org/doc/draft-ietf-acme-ari/)).
|
||||||
|
This module implements version 3 of the ARI draft."
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.no_account
|
||||||
|
- community.crypto.attributes
|
||||||
|
- community.crypto.attributes.info_module
|
||||||
|
options:
|
||||||
|
certificate_path:
|
||||||
|
description:
|
||||||
|
- A path to the X.509 certificate to request information for.
|
||||||
|
- Exactly one of O(certificate_path) and O(certificate_content) must be provided.
|
||||||
|
type: path
|
||||||
|
certificate_content:
|
||||||
|
description:
|
||||||
|
- The content of the X.509 certificate to request information for.
|
||||||
|
- Exactly one of O(certificate_path) and O(certificate_content) must be provided.
|
||||||
|
type: str
|
||||||
|
seealso:
|
||||||
|
- module: community.crypto.acme_certificate
|
||||||
|
description: Allows to obtain a certificate using the ACME protocol
|
||||||
|
- module: community.crypto.acme_certificate_revoke
|
||||||
|
description: Allows to revoke a certificate using the ACME protocol
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Retrieve renewal information for a certificate
|
||||||
|
community.crypto.acme_ari_info:
|
||||||
|
certificate_path: /etc/httpd/ssl/sample.com.crt
|
||||||
|
register: cert_data
|
||||||
|
|
||||||
|
- name: Show the certificate renewal information
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cert_data.renewal_info
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
renewal_info:
|
||||||
|
description: The ARI renewal info object (U(https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.2)).
|
||||||
|
returned: success
|
||||||
|
type: dict
|
||||||
|
contains:
|
||||||
|
suggestedWindow:
|
||||||
|
description:
|
||||||
|
- Describes the window during which the certificate should be renewed.
|
||||||
|
type: dict
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
start:
|
||||||
|
description:
|
||||||
|
- The start of the window during which the certificate should be renewed.
|
||||||
|
- The format is specified in L(RFC 3339,https://www.rfc-editor.org/info/rfc3339).
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: '2021-01-03T00:00:00Z'
|
||||||
|
end:
|
||||||
|
description:
|
||||||
|
- The end of the window during which the certificate should be renewed.
|
||||||
|
- The format is specified in L(RFC 3339,https://www.rfc-editor.org/info/rfc3339).
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: '2021-01-03T00:00:00Z'
|
||||||
|
explanationURL:
|
||||||
|
description:
|
||||||
|
- A URL pointing to a page which may explain why the suggested renewal window is what it is.
|
||||||
|
- For example, it may be a page explaining the CA's dynamic load-balancing strategy, or a
|
||||||
|
page documenting which certificates are affected by a mass revocation event. Should be shown
|
||||||
|
to the user.
|
||||||
|
returned: depends on the ACME server
|
||||||
|
type: str
|
||||||
|
sample: https://example.com/docs/ari
|
||||||
|
retryAfter:
|
||||||
|
description:
|
||||||
|
- A timestamp before the next retry to ask for this information should not be made.
|
||||||
|
returned: depends on the ACME server
|
||||||
|
type: str
|
||||||
|
sample: '2024-04-29T01:17:10.236921+00:00'
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
|
create_backend,
|
||||||
|
create_default_argspec,
|
||||||
|
ACMEClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = create_default_argspec(with_account=False)
|
||||||
|
argument_spec.update_argspec(
|
||||||
|
certificate_path=dict(type='path'),
|
||||||
|
certificate_content=dict(type='str'),
|
||||||
|
)
|
||||||
|
argument_spec.update(
|
||||||
|
required_one_of=(
|
||||||
|
['certificate_path', 'certificate_content'],
|
||||||
|
),
|
||||||
|
mutually_exclusive=(
|
||||||
|
['certificate_path', 'certificate_content'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
|
backend = create_backend(module, True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = ACMEClient(module, backend)
|
||||||
|
if not client.directory.has_renewal_info_endpoint():
|
||||||
|
module.fail_json(msg='The ACME endpoint does not support ACME Renewal Information retrieval')
|
||||||
|
renewal_info = client.get_renewal_info(
|
||||||
|
cert_filename=module.params['certificate_path'],
|
||||||
|
cert_content=module.params['certificate_content'],
|
||||||
|
include_retry_after=True,
|
||||||
|
)
|
||||||
|
module.exit_json(renewal_info=renewal_info)
|
||||||
|
except ModuleFailException as e:
|
||||||
|
e.do_fail(module)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -19,15 +19,15 @@ description:
|
|||||||
L(ACME protocol,https://tools.ietf.org/html/rfc8555),
|
L(ACME protocol,https://tools.ietf.org/html/rfc8555),
|
||||||
such as L(Let's Encrypt,https://letsencrypt.org/) or
|
such as L(Let's Encrypt,https://letsencrypt.org/) or
|
||||||
L(Buypass,https://www.buypass.com/). The current implementation
|
L(Buypass,https://www.buypass.com/). The current implementation
|
||||||
supports the C(http-01), C(dns-01) and C(tls-alpn-01) challenges."
|
supports the V(http-01), V(dns-01) and V(tls-alpn-01) challenges."
|
||||||
- "To use this module, it has to be executed twice. Either as two
|
- "To use this module, it has to be executed twice. Either as two
|
||||||
different tasks in the same run or during two runs. Note that the output
|
different tasks in the same run or during two runs. Note that the output
|
||||||
of the first run needs to be recorded and passed to the second run as the
|
of the first run needs to be recorded and passed to the second run as the
|
||||||
module argument C(data)."
|
module argument O(data)."
|
||||||
- "Between these two tasks you have to fulfill the required steps for the
|
- "Between these two tasks you have to fulfill the required steps for the
|
||||||
chosen challenge by whatever means necessary. For C(http-01) that means
|
chosen challenge by whatever means necessary. For V(http-01) that means
|
||||||
creating the necessary challenge file on the destination webserver. For
|
creating the necessary challenge file on the destination webserver. For
|
||||||
C(dns-01) the necessary dns record has to be created. For C(tls-alpn-01)
|
V(dns-01) the necessary dns record has to be created. For V(tls-alpn-01)
|
||||||
the necessary certificate has to be created and served.
|
the necessary certificate has to be created and served.
|
||||||
It is I(not) the responsibility of this module to perform these steps."
|
It is I(not) the responsibility of this module to perform these steps."
|
||||||
- "For details on how to fulfill these challenges, you might have to read through
|
- "For details on how to fulfill these challenges, you might have to read through
|
||||||
@@ -37,11 +37,11 @@ description:
|
|||||||
- "The module includes experimental support for IP identifiers according to
|
- "The module includes experimental support for IP identifiers according to
|
||||||
the L(RFC 8738,https://www.rfc-editor.org/rfc/rfc8738.html)."
|
the L(RFC 8738,https://www.rfc-editor.org/rfc/rfc8738.html)."
|
||||||
notes:
|
notes:
|
||||||
- "At least one of C(dest) and C(fullchain_dest) must be specified."
|
- "At least one of O(dest) and O(fullchain_dest) must be specified."
|
||||||
- "This module includes basic account management functionality.
|
- "This module includes basic account management functionality.
|
||||||
If you want to have more control over your ACME account, use the
|
If you want to have more control over your ACME account, use the
|
||||||
M(community.crypto.acme_account) module and disable account management
|
M(community.crypto.acme_account) module and disable account management
|
||||||
for this module using the C(modify_account) option."
|
for this module using the O(modify_account) option."
|
||||||
- "This module was called C(letsencrypt) before Ansible 2.6. The usage
|
- "This module was called C(letsencrypt) before Ansible 2.6. The usage
|
||||||
did not change."
|
did not change."
|
||||||
seealso:
|
seealso:
|
||||||
@@ -57,10 +57,10 @@ seealso:
|
|||||||
description: The specification of the ACME protocol (RFC 8555).
|
description: The specification of the ACME protocol (RFC 8555).
|
||||||
link: https://tools.ietf.org/html/rfc8555
|
link: https://tools.ietf.org/html/rfc8555
|
||||||
- name: ACME TLS ALPN Challenge Extension
|
- name: ACME TLS ALPN Challenge Extension
|
||||||
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
|
description: The specification of the V(tls-alpn-01) challenge (RFC 8737).
|
||||||
link: https://www.rfc-editor.org/rfc/rfc8737.html-05
|
link: https://www.rfc-editor.org/rfc/rfc8737.html
|
||||||
- module: community.crypto.acme_challenge_cert_helper
|
- module: community.crypto.acme_challenge_cert_helper
|
||||||
description: Helps preparing C(tls-alpn-01) challenges.
|
description: Helps preparing V(tls-alpn-01) challenges.
|
||||||
- module: community.crypto.openssl_privatekey
|
- module: community.crypto.openssl_privatekey
|
||||||
description: Can be used to create private keys (both for certificates and accounts).
|
description: Can be used to create private keys (both for certificates and accounts).
|
||||||
- module: community.crypto.openssl_privatekey_pipe
|
- module: community.crypto.openssl_privatekey_pipe
|
||||||
@@ -77,8 +77,12 @@ seealso:
|
|||||||
description: Allows to create, modify or delete an ACME account.
|
description: Allows to create, modify or delete an ACME account.
|
||||||
- module: community.crypto.acme_inspect
|
- module: community.crypto.acme_inspect
|
||||||
description: Allows to debug problems.
|
description: Allows to debug problems.
|
||||||
|
- module: community.crypto.acme_certificate_deactivate_authz
|
||||||
|
description: Allows to deactivate (invalidate) ACME v2 orders.
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.acme
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.account
|
||||||
|
- community.crypto.acme.certificate
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
- community.crypto.attributes.files
|
- community.crypto.attributes.files
|
||||||
- community.crypto.attributes.actiongroup_acme
|
- community.crypto.attributes.actiongroup_acme
|
||||||
@@ -94,7 +98,7 @@ options:
|
|||||||
description:
|
description:
|
||||||
- "The email address associated with this account."
|
- "The email address associated with this account."
|
||||||
- "It will be used for certificate expiration warnings."
|
- "It will be used for certificate expiration warnings."
|
||||||
- "Note that when C(modify_account) is not set to C(false) and you also
|
- "Note that when O(modify_account) is not set to V(false) and you also
|
||||||
used the M(community.crypto.acme_account) module to specify more than one contact
|
used the M(community.crypto.acme_account) module to specify more than one contact
|
||||||
for your account, this module will update your account and restrict
|
for your account, this module will update your account and restrict
|
||||||
it to the (at most one) contact email address specified here."
|
it to the (at most one) contact email address specified here."
|
||||||
@@ -102,59 +106,44 @@ options:
|
|||||||
agreement:
|
agreement:
|
||||||
description:
|
description:
|
||||||
- "URI to a terms of service document you agree to when using the
|
- "URI to a terms of service document you agree to when using the
|
||||||
ACME v1 service at C(acme_directory)."
|
ACME v1 service at O(acme_directory)."
|
||||||
- Default is latest gathered from C(acme_directory) URL.
|
- Default is latest gathered from O(acme_directory) URL.
|
||||||
- This option will only be used when C(acme_version) is 1.
|
- This option will only be used when O(acme_version) is 1.
|
||||||
type: str
|
type: str
|
||||||
terms_agreed:
|
terms_agreed:
|
||||||
description:
|
description:
|
||||||
- "Boolean indicating whether you agree to the terms of service document."
|
- "Boolean indicating whether you agree to the terms of service document."
|
||||||
- "ACME servers can require this to be true."
|
- "ACME servers can require this to be true."
|
||||||
- This option will only be used when C(acme_version) is not 1.
|
- This option will only be used when O(acme_version) is not 1.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
modify_account:
|
modify_account:
|
||||||
description:
|
description:
|
||||||
- "Boolean indicating whether the module should create the account if
|
- "Boolean indicating whether the module should create the account if
|
||||||
necessary, and update its contact data."
|
necessary, and update its contact data."
|
||||||
- "Set to C(false) if you want to use the M(community.crypto.acme_account) module to manage
|
- "Set to V(false) if you want to use the M(community.crypto.acme_account) module to manage
|
||||||
your account instead, and to avoid accidental creation of a new account
|
your account instead, and to avoid accidental creation of a new account
|
||||||
using an old key if you changed the account key with M(community.crypto.acme_account)."
|
using an old key if you changed the account key with M(community.crypto.acme_account)."
|
||||||
- "If set to C(false), C(terms_agreed) and C(account_email) are ignored."
|
- "If set to V(false), O(terms_agreed) and O(account_email) are ignored."
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
challenge:
|
challenge:
|
||||||
description: The challenge to be performed.
|
description:
|
||||||
|
- The challenge to be performed.
|
||||||
|
- If set to V(no challenge), no challenge will be used. This is necessary for some private
|
||||||
|
CAs which use External Account Binding and other means of validating certificate assurance.
|
||||||
|
For example, an account could be allowed to issue certificates for C(foo.example.com)
|
||||||
|
without any further validation for a certain period of time.
|
||||||
type: str
|
type: str
|
||||||
default: 'http-01'
|
default: 'http-01'
|
||||||
choices: [ 'http-01', 'dns-01', 'tls-alpn-01' ]
|
choices:
|
||||||
|
- 'http-01'
|
||||||
|
- 'dns-01'
|
||||||
|
- 'tls-alpn-01'
|
||||||
|
- 'no challenge'
|
||||||
csr:
|
csr:
|
||||||
description:
|
|
||||||
- "File containing the CSR for the new certificate."
|
|
||||||
- "Can be created with M(community.crypto.openssl_csr) or C(openssl req ...)."
|
|
||||||
- "The CSR may contain multiple Subject Alternate Names, but each one
|
|
||||||
will lead to an individual challenge that must be fulfilled for the
|
|
||||||
CSR to be signed."
|
|
||||||
- "I(Note): the private key used to create the CSR I(must not) be the
|
|
||||||
account key. This is a bad idea from a security point of view, and
|
|
||||||
the CA should not accept the CSR. The ACME server should return an
|
|
||||||
error in this case."
|
|
||||||
- Precisely one of I(csr) or I(csr_content) must be specified.
|
|
||||||
type: path
|
|
||||||
aliases: ['src']
|
aliases: ['src']
|
||||||
csr_content:
|
csr_content:
|
||||||
description:
|
|
||||||
- "Content of the CSR for the new certificate."
|
|
||||||
- "Can be created with M(community.crypto.openssl_csr_pipe) or C(openssl req ...)."
|
|
||||||
- "The CSR may contain multiple Subject Alternate Names, but each one
|
|
||||||
will lead to an individual challenge that must be fulfilled for the
|
|
||||||
CSR to be signed."
|
|
||||||
- "I(Note): the private key used to create the CSR I(must not) be the
|
|
||||||
account key. This is a bad idea from a security point of view, and
|
|
||||||
the CA should not accept the CSR. The ACME server should return an
|
|
||||||
error in this case."
|
|
||||||
- Precisely one of I(csr) or I(csr_content) must be specified.
|
|
||||||
type: str
|
|
||||||
version_added: 1.2.0
|
version_added: 1.2.0
|
||||||
data:
|
data:
|
||||||
description:
|
description:
|
||||||
@@ -162,27 +151,27 @@ options:
|
|||||||
the second run of the module only."
|
the second run of the module only."
|
||||||
- "The value that must be used here will be provided by a previous use
|
- "The value that must be used here will be provided by a previous use
|
||||||
of this module. See the examples for more details."
|
of this module. See the examples for more details."
|
||||||
- "Note that for ACME v2, only the C(order_uri) entry of C(data) will
|
- "Note that for ACME v2, only the C(order_uri) entry of O(data) will
|
||||||
be used. For ACME v1, C(data) must be non-empty to indicate the
|
be used. For ACME v1, O(data) must be non-empty to indicate the
|
||||||
second stage is active; all needed data will be taken from the
|
second stage is active; all needed data will be taken from the
|
||||||
CSR."
|
CSR."
|
||||||
- "I(Note): the C(data) option was marked as C(no_log) up to
|
- "I(Note): the O(data) option was marked as C(no_log) up to
|
||||||
Ansible 2.5. From Ansible 2.6 on, it is no longer marked this way
|
Ansible 2.5. From Ansible 2.6 on, it is no longer marked this way
|
||||||
as it causes error messages to be come unusable, and C(data) does
|
as it causes error messages to be come unusable, and O(data) does
|
||||||
not contain any information which can be used without having
|
not contain any information which can be used without having
|
||||||
access to the account key or which are not public anyway."
|
access to the account key or which are not public anyway."
|
||||||
type: dict
|
type: dict
|
||||||
dest:
|
dest:
|
||||||
description:
|
description:
|
||||||
- "The destination file for the certificate."
|
- "The destination file for the certificate."
|
||||||
- "Required if C(fullchain_dest) is not specified."
|
- "Required if O(fullchain_dest) is not specified."
|
||||||
type: path
|
type: path
|
||||||
aliases: ['cert']
|
aliases: ['cert']
|
||||||
fullchain_dest:
|
fullchain_dest:
|
||||||
description:
|
description:
|
||||||
- "The destination file for the full chain (that is, a certificate followed
|
- "The destination file for the full chain (that is, a certificate followed
|
||||||
by chain of intermediate certificates)."
|
by chain of intermediate certificates)."
|
||||||
- "Required if C(dest) is not specified."
|
- "Required if O(dest) is not specified."
|
||||||
type: path
|
type: path
|
||||||
aliases: ['fullchain']
|
aliases: ['fullchain']
|
||||||
chain_dest:
|
chain_dest:
|
||||||
@@ -193,11 +182,11 @@ options:
|
|||||||
remaining_days:
|
remaining_days:
|
||||||
description:
|
description:
|
||||||
- "The number of days the certificate must have left being valid.
|
- "The number of days the certificate must have left being valid.
|
||||||
If C(cert_days < remaining_days), then it will be renewed.
|
If RV(cert_days) < O(remaining_days), then it will be renewed.
|
||||||
If the certificate is not renewed, module return values will not
|
If the certificate is not renewed, module return values will not
|
||||||
include C(challenge_data)."
|
include RV(challenge_data)."
|
||||||
- "To make sure that the certificate is renewed in any case, you can
|
- "To make sure that the certificate is renewed in any case, you can
|
||||||
use the C(force) option."
|
use the O(force) option."
|
||||||
type: int
|
type: int
|
||||||
default: 10
|
default: 10
|
||||||
deactivate_authzs:
|
deactivate_authzs:
|
||||||
@@ -213,16 +202,16 @@ options:
|
|||||||
force:
|
force:
|
||||||
description:
|
description:
|
||||||
- Enforces the execution of the challenge and validation, even if an
|
- Enforces the execution of the challenge and validation, even if an
|
||||||
existing certificate is still valid for more than C(remaining_days).
|
existing certificate is still valid for more than O(remaining_days).
|
||||||
- This is especially helpful when having an updated CSR, for example with
|
- This is especially helpful when having an updated CSR, for example with
|
||||||
additional domains for which a new certificate is desired.
|
additional domains for which a new certificate is desired.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
retrieve_all_alternates:
|
retrieve_all_alternates:
|
||||||
description:
|
description:
|
||||||
- "When set to C(true), will retrieve all alternate trust chains offered by the ACME CA.
|
- "When set to V(true), will retrieve all alternate trust chains offered by the ACME CA.
|
||||||
These will not be written to disk, but will be returned together with the main
|
These will not be written to disk, but will be returned together with the main
|
||||||
chain as C(all_chains). See the documentation for the C(all_chains) return
|
chain as RV(all_chains). See the documentation for the RV(all_chains) return
|
||||||
value for details."
|
value for details."
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
@@ -235,8 +224,8 @@ options:
|
|||||||
- "If a criterium matches multiple chains, the first one matching will be
|
- "If a criterium matches multiple chains, the first one matching will be
|
||||||
returned. The order is determined by the ordering of the C(Link) headers
|
returned. The order is determined by the ordering of the C(Link) headers
|
||||||
returned by the ACME server and might not be deterministic."
|
returned by the ACME server and might not be deterministic."
|
||||||
- "Every criterium can consist of multiple different conditions, like I(issuer)
|
- "Every criterium can consist of multiple different conditions, like O(select_chain[].issuer)
|
||||||
and I(subject). For the criterium to match a chain, all conditions must apply
|
and O(select_chain[].subject). For the criterium to match a chain, all conditions must apply
|
||||||
to the same certificate in the chain."
|
to the same certificate in the chain."
|
||||||
- "This option can only be used with the C(cryptography) backend."
|
- "This option can only be used with the C(cryptography) backend."
|
||||||
type: list
|
type: list
|
||||||
@@ -246,11 +235,11 @@ options:
|
|||||||
test_certificates:
|
test_certificates:
|
||||||
description:
|
description:
|
||||||
- "Determines which certificates in the chain will be tested."
|
- "Determines which certificates in the chain will be tested."
|
||||||
- "I(all) tests all certificates in the chain (excluding the leaf, which is
|
- "V(all) tests all certificates in the chain (excluding the leaf, which is
|
||||||
identical in all chains)."
|
identical in all chains)."
|
||||||
- "I(first) only tests the first certificate in the chain, that is the one which
|
- "V(first) only tests the first certificate in the chain, that is the one which
|
||||||
signed the leaf."
|
signed the leaf."
|
||||||
- "I(last) only tests the last certificate in the chain, that is the one furthest
|
- "V(last) only tests the last certificate in the chain, that is the one furthest
|
||||||
away from the leaf. Its issuer is the root certificate of this chain."
|
away from the leaf. Its issuer is the root certificate of this chain."
|
||||||
type: str
|
type: str
|
||||||
default: all
|
default: all
|
||||||
@@ -259,30 +248,56 @@ options:
|
|||||||
description:
|
description:
|
||||||
- "Allows to specify parts of the issuer of a certificate in the chain must
|
- "Allows to specify parts of the issuer of a certificate in the chain must
|
||||||
have to be selected."
|
have to be selected."
|
||||||
- "If I(issuer) is empty, any certificate will match."
|
- "If O(select_chain[].issuer) is empty, any certificate will match."
|
||||||
- 'An example value would be C({"commonName": "My Preferred CA Root"}).'
|
- 'An example value would be V({"commonName": "My Preferred CA Root"}).'
|
||||||
type: dict
|
type: dict
|
||||||
subject:
|
subject:
|
||||||
description:
|
description:
|
||||||
- "Allows to specify parts of the subject of a certificate in the chain must
|
- "Allows to specify parts of the subject of a certificate in the chain must
|
||||||
have to be selected."
|
have to be selected."
|
||||||
- "If I(subject) is empty, any certificate will match."
|
- "If O(select_chain[].subject) is empty, any certificate will match."
|
||||||
- 'An example value would be C({"CN": "My Preferred CA Intermediate"})'
|
- 'An example value would be V({"CN": "My Preferred CA Intermediate"})'
|
||||||
type: dict
|
type: dict
|
||||||
subject_key_identifier:
|
subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- "Checks for the SubjectKeyIdentifier extension. This is an identifier based
|
- "Checks for the SubjectKeyIdentifier extension. This is an identifier based
|
||||||
on the private key of the intermediate certificate."
|
on the private key of the intermediate certificate."
|
||||||
- "The identifier must be of the form
|
- "The identifier must be of the form
|
||||||
C(A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1)."
|
V(A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1)."
|
||||||
type: str
|
type: str
|
||||||
authority_key_identifier:
|
authority_key_identifier:
|
||||||
description:
|
description:
|
||||||
- "Checks for the AuthorityKeyIdentifier extension. This is an identifier based
|
- "Checks for the AuthorityKeyIdentifier extension. This is an identifier based
|
||||||
on the private key of the issuer of the intermediate certificate."
|
on the private key of the issuer of the intermediate certificate."
|
||||||
- "The identifier must be of the form
|
- "The identifier must be of the form
|
||||||
C(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
|
V(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
|
||||||
type: str
|
type: str
|
||||||
|
include_renewal_cert_id:
|
||||||
|
description:
|
||||||
|
- Determines whether to request renewal of an existing certificate according to
|
||||||
|
L(the ACME ARI draft 3, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-5).
|
||||||
|
- This is only used when the certificate specified in O(dest) or O(fullchain_dest) already exists.
|
||||||
|
- V(never) never sends the certificate ID of the certificate to renew. V(always) will always send it.
|
||||||
|
- V(when_ari_supported) only sends the certificate ID if the ARI endpoint is found in the ACME directory.
|
||||||
|
- Generally you should use V(when_ari_supported) if you know that the ACME service supports a compatible
|
||||||
|
draft (or final version, once it is out) of the ARI extension. V(always) should never be necessary.
|
||||||
|
If you are not sure, or if you receive strange errors on invalid C(replaces) values in order objects,
|
||||||
|
use V(never), which also happens to be the default.
|
||||||
|
- ACME servers might refuse to create new orders with C(replaces) for certificates that already have an
|
||||||
|
existing order. This can happen if this module is used to create an order, and then the playbook/role
|
||||||
|
fails in case the challenges cannot be set up. If the playbook/role does not record the order data to
|
||||||
|
continue with the existing order, but tries to create a new one on the next run, creating the new order
|
||||||
|
might fail. For this reason, this option should only be set to a value different from V(never) if the
|
||||||
|
role/playbook using it keeps track of order data accross restarts, or if it takes care to deactivate
|
||||||
|
orders whose processing is aborted. Orders can be deactivated with the
|
||||||
|
M(community.crypto.acme_certificate_deactivate_authz) module.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- never
|
||||||
|
- when_ari_supported
|
||||||
|
- always
|
||||||
|
default: never
|
||||||
|
version_added: 2.20.0
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
@@ -296,9 +311,10 @@ EXAMPLES = r'''
|
|||||||
register: sample_com_challenge
|
register: sample_com_challenge
|
||||||
|
|
||||||
# Alternative first step:
|
# Alternative first step:
|
||||||
- name: Create a challenge for sample.com using a account key from hashi vault.
|
- name: Create a challenge for sample.com using a account key from Hashi Vault.
|
||||||
community.crypto.acme_certificate:
|
community.crypto.acme_certificate:
|
||||||
account_key_content: "{{ lookup('hashi_vault', 'secret=secret/account_private_key:value') }}"
|
account_key_content: >-
|
||||||
|
{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/account_private_key:value') }}
|
||||||
csr: /etc/pki/cert/csr/sample.com.csr
|
csr: /etc/pki/cert/csr/sample.com.csr
|
||||||
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
|
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
|
||||||
register: sample_com_challenge
|
register: sample_com_challenge
|
||||||
@@ -315,14 +331,16 @@ EXAMPLES = r'''
|
|||||||
# perform the necessary steps to fulfill the challenge
|
# perform the necessary steps to fulfill the challenge
|
||||||
# for example:
|
# for example:
|
||||||
#
|
#
|
||||||
# - copy:
|
# - name: Copy http-01 challenge for sample.com
|
||||||
|
# ansible.builtin.copy:
|
||||||
# dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }}
|
# dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }}
|
||||||
# content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}"
|
# content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}"
|
||||||
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge['challenge_data']
|
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge['challenge_data']
|
||||||
#
|
#
|
||||||
# Alternative way:
|
# Alternative way:
|
||||||
#
|
#
|
||||||
# - copy:
|
# - name: Copy http-01 challenges
|
||||||
|
# ansible.builtin.copy:
|
||||||
# dest: /var/www/{{ item.key }}/{{ item.value['http-01']['resource'] }}
|
# dest: /var/www/{{ item.key }}/{{ item.value['http-01']['resource'] }}
|
||||||
# content: "{{ item.value['http-01']['resource_value'] }}"
|
# content: "{{ item.value['http-01']['resource_value'] }}"
|
||||||
# loop: "{{ sample_com_challenge.challenge_data | dict2items }}"
|
# loop: "{{ sample_com_challenge.challenge_data | dict2items }}"
|
||||||
@@ -354,7 +372,8 @@ EXAMPLES = r'''
|
|||||||
# perform the necessary steps to fulfill the challenge
|
# perform the necessary steps to fulfill the challenge
|
||||||
# for example:
|
# for example:
|
||||||
#
|
#
|
||||||
# - community.aws.route53:
|
# - name: Create DNS record for sample.com dns-01 challenge
|
||||||
|
# community.aws.route53:
|
||||||
# zone: sample.com
|
# zone: sample.com
|
||||||
# record: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].record }}"
|
# record: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].record }}"
|
||||||
# type: TXT
|
# type: TXT
|
||||||
@@ -362,12 +381,13 @@ EXAMPLES = r'''
|
|||||||
# state: present
|
# state: present
|
||||||
# wait: true
|
# wait: true
|
||||||
# # Note: route53 requires TXT entries to be enclosed in quotes
|
# # Note: route53 requires TXT entries to be enclosed in quotes
|
||||||
# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | regex_replace('^(.*)$', '\"\\1\"') }}"
|
# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | community.dns.quote_txt(always_quote=true) }}"
|
||||||
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data
|
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data
|
||||||
#
|
#
|
||||||
# Alternative way:
|
# Alternative way:
|
||||||
#
|
#
|
||||||
# - community.aws.route53:
|
# - name: Create DNS records for dns-01 challenges
|
||||||
|
# community.aws.route53:
|
||||||
# zone: sample.com
|
# zone: sample.com
|
||||||
# record: "{{ item.key }}"
|
# record: "{{ item.key }}"
|
||||||
# type: TXT
|
# type: TXT
|
||||||
@@ -376,7 +396,7 @@ EXAMPLES = r'''
|
|||||||
# wait: true
|
# wait: true
|
||||||
# # Note: item.value is a list of TXT entries, and route53
|
# # Note: item.value is a list of TXT entries, and route53
|
||||||
# # requires every entry to be enclosed in quotes
|
# # requires every entry to be enclosed in quotes
|
||||||
# value: "{{ item.value | map('regex_replace', '^(.*)$', '\"\\1\"' ) | list }}"
|
# value: "{{ item.value | map('community.dns.quote_txt', always_quote=true) | list }}"
|
||||||
# loop: "{{ sample_com_challenge.challenge_data_dns | dict2items }}"
|
# loop: "{{ sample_com_challenge.challenge_data_dns | dict2items }}"
|
||||||
# when: sample_com_challenge is changed
|
# when: sample_com_challenge is changed
|
||||||
|
|
||||||
@@ -432,42 +452,58 @@ challenge_data:
|
|||||||
- Per identifier / challenge type challenge data.
|
- Per identifier / challenge type challenge data.
|
||||||
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
|
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
|
||||||
returned: changed
|
returned: changed
|
||||||
type: list
|
type: dict
|
||||||
elements: dict
|
|
||||||
contains:
|
contains:
|
||||||
resource:
|
identifier:
|
||||||
description: The challenge resource that must be created for validation.
|
|
||||||
returned: changed
|
|
||||||
type: str
|
|
||||||
sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA
|
|
||||||
resource_original:
|
|
||||||
description:
|
description:
|
||||||
- The original challenge resource including type identifier for C(tls-alpn-01)
|
- For every identifier, provides a dictionary of challenge types mapping to challenge data.
|
||||||
challenges.
|
- The keys in this dictionary are the identifiers. C(identifier) is a placeholder used in the documentation.
|
||||||
returned: changed and challenge is C(tls-alpn-01)
|
- Note that the keys are not valid Jinja2 identifiers.
|
||||||
type: str
|
|
||||||
sample: DNS:example.com
|
|
||||||
resource_value:
|
|
||||||
description:
|
|
||||||
- The value the resource has to produce for the validation.
|
|
||||||
- For C(http-01) and C(dns-01) challenges, the value can be used as-is.
|
|
||||||
- "For C(tls-alpn-01) challenges, note that this return value contains a
|
|
||||||
Base64 encoded version of the correct binary blob which has to be put
|
|
||||||
into the acmeValidation x509 extension; see
|
|
||||||
U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3)
|
|
||||||
for details. To do this, you might need the C(b64decode) Jinja filter
|
|
||||||
to extract the binary blob from this return value."
|
|
||||||
returned: changed
|
returned: changed
|
||||||
type: str
|
type: dict
|
||||||
sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA
|
contains:
|
||||||
record:
|
challenge-type:
|
||||||
description: The full DNS record's name for the challenge.
|
description:
|
||||||
returned: changed and challenge is C(dns-01)
|
- Data for every challenge type.
|
||||||
type: str
|
- The keys in this dictionary are the challenge types. C(challenge-type) is a placeholder used in the documentation.
|
||||||
sample: _acme-challenge.example.com
|
Possible keys are V(http-01), V(dns-01), and V(tls-alpn-01).
|
||||||
|
- Note that the keys are not valid Jinja2 identifiers.
|
||||||
|
returned: changed
|
||||||
|
type: dict
|
||||||
|
contains:
|
||||||
|
resource:
|
||||||
|
description: The challenge resource that must be created for validation.
|
||||||
|
returned: changed
|
||||||
|
type: str
|
||||||
|
sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA
|
||||||
|
resource_original:
|
||||||
|
description:
|
||||||
|
- The original challenge resource including type identifier for V(tls-alpn-01)
|
||||||
|
challenges.
|
||||||
|
returned: changed and O(challenge) is V(tls-alpn-01)
|
||||||
|
type: str
|
||||||
|
sample: DNS:example.com
|
||||||
|
resource_value:
|
||||||
|
description:
|
||||||
|
- The value the resource has to produce for the validation.
|
||||||
|
- For V(http-01) and V(dns-01) challenges, the value can be used as-is.
|
||||||
|
- "For V(tls-alpn-01) challenges, note that this return value contains a
|
||||||
|
Base64 encoded version of the correct binary blob which has to be put
|
||||||
|
into the acmeValidation x509 extension; see
|
||||||
|
U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3)
|
||||||
|
for details. To do this, you might need the P(ansible.builtin.b64decode#filter) Jinja filter
|
||||||
|
to extract the binary blob from this return value."
|
||||||
|
returned: changed
|
||||||
|
type: str
|
||||||
|
sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA
|
||||||
|
record:
|
||||||
|
description: The full DNS record's name for the challenge.
|
||||||
|
returned: changed and challenge is V(dns-01)
|
||||||
|
type: str
|
||||||
|
sample: _acme-challenge.example.com
|
||||||
challenge_data_dns:
|
challenge_data_dns:
|
||||||
description:
|
description:
|
||||||
- List of TXT values per DNS record, in case challenge is C(dns-01).
|
- List of TXT values per DNS record, in case challenge is V(dns-01).
|
||||||
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
|
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
|
||||||
returned: changed
|
returned: changed
|
||||||
type: dict
|
type: dict
|
||||||
@@ -505,11 +541,11 @@ account_uri:
|
|||||||
type: str
|
type: str
|
||||||
all_chains:
|
all_chains:
|
||||||
description:
|
description:
|
||||||
- When I(retrieve_all_alternates) is set to C(true), the module will query the ACME server
|
- When O(retrieve_all_alternates) is set to V(true), the module will query the ACME server
|
||||||
for alternate chains. This return value will contain a list of all chains returned,
|
for alternate chains. This return value will contain a list of all chains returned,
|
||||||
the first entry being the main chain returned by the server.
|
the first entry being the main chain returned by the server.
|
||||||
- See L(Section 7.4.2 of RFC8555,https://tools.ietf.org/html/rfc8555#section-7.4.2) for details.
|
- See L(Section 7.4.2 of RFC8555,https://tools.ietf.org/html/rfc8555#section-7.4.2) for details.
|
||||||
returned: when certificate was retrieved and I(retrieve_all_alternates) is set to C(true)
|
returned: when certificate was retrieved and O(retrieve_all_alternates) is set to V(true)
|
||||||
type: list
|
type: list
|
||||||
elements: dict
|
elements: dict
|
||||||
contains:
|
contains:
|
||||||
@@ -533,11 +569,9 @@ all_chains:
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
create_backend,
|
create_backend,
|
||||||
get_default_argspec,
|
create_default_argspec,
|
||||||
ACMEClient,
|
ACMEClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -548,6 +582,7 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.account impo
|
|||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
|
||||||
combine_identifier,
|
combine_identifier,
|
||||||
split_identifier,
|
split_identifier,
|
||||||
|
wait_for_validation,
|
||||||
Authorization,
|
Authorization,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -570,10 +605,14 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.orders impor
|
|||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||||
|
compute_cert_id,
|
||||||
pem_to_der,
|
pem_to_der,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
NO_CHALLENGE = 'no challenge'
|
||||||
|
|
||||||
|
|
||||||
class ACMECertificateClient(object):
|
class ACMECertificateClient(object):
|
||||||
'''
|
'''
|
||||||
ACME client class. Uses an ACME account object and a CSR to
|
ACME client class. Uses an ACME account object and a CSR to
|
||||||
@@ -585,6 +624,9 @@ class ACMECertificateClient(object):
|
|||||||
self.module = module
|
self.module = module
|
||||||
self.version = module.params['acme_version']
|
self.version = module.params['acme_version']
|
||||||
self.challenge = module.params['challenge']
|
self.challenge = module.params['challenge']
|
||||||
|
# We use None instead of a magic string for 'no challenge'
|
||||||
|
if self.challenge == NO_CHALLENGE:
|
||||||
|
self.challenge = None
|
||||||
self.csr = module.params['csr']
|
self.csr = module.params['csr']
|
||||||
self.csr_content = module.params['csr_content']
|
self.csr_content = module.params['csr_content']
|
||||||
self.dest = module.params.get('dest')
|
self.dest = module.params.get('dest')
|
||||||
@@ -600,6 +642,7 @@ class ACMECertificateClient(object):
|
|||||||
self.order_uri = self.data.get('order_uri') if self.data else None
|
self.order_uri = self.data.get('order_uri') if self.data else None
|
||||||
self.all_chains = None
|
self.all_chains = None
|
||||||
self.select_chain_matcher = []
|
self.select_chain_matcher = []
|
||||||
|
self.include_renewal_cert_id = module.params['include_renewal_cert_id']
|
||||||
|
|
||||||
if self.module.params['select_chain']:
|
if self.module.params['select_chain']:
|
||||||
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
|
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
|
||||||
@@ -640,7 +683,7 @@ class ACMECertificateClient(object):
|
|||||||
raise ModuleFailException("CSR %s not found" % (self.csr))
|
raise ModuleFailException("CSR %s not found" % (self.csr))
|
||||||
|
|
||||||
# Extract list of identifiers from CSR
|
# Extract list of identifiers from CSR
|
||||||
self.identifiers = self.client.backend.get_csr_identifiers(csr_filename=self.csr, csr_content=self.csr_content)
|
self.identifiers = self.client.backend.get_ordered_csr_identifiers(csr_filename=self.csr, csr_content=self.csr_content)
|
||||||
|
|
||||||
def is_first_step(self):
|
def is_first_step(self):
|
||||||
'''
|
'''
|
||||||
@@ -657,6 +700,15 @@ class ACMECertificateClient(object):
|
|||||||
# stored in self.order_uri by the constructor).
|
# stored in self.order_uri by the constructor).
|
||||||
return self.order_uri is None
|
return self.order_uri is None
|
||||||
|
|
||||||
|
def _get_cert_info_or_none(self):
|
||||||
|
if self.module.params.get('dest'):
|
||||||
|
filename = self.module.params['dest']
|
||||||
|
else:
|
||||||
|
filename = self.module.params['fullchain_dest']
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
return None
|
||||||
|
return self.client.backend.get_cert_information(cert_filename=filename)
|
||||||
|
|
||||||
def start_challenges(self):
|
def start_challenges(self):
|
||||||
'''
|
'''
|
||||||
Create new authorizations for all identifiers of the CSR,
|
Create new authorizations for all identifiers of the CSR,
|
||||||
@@ -671,7 +723,19 @@ class ACMECertificateClient(object):
|
|||||||
authz = Authorization.create(self.client, identifier_type, identifier)
|
authz = Authorization.create(self.client, identifier_type, identifier)
|
||||||
self.authorizations[authz.combined_identifier] = authz
|
self.authorizations[authz.combined_identifier] = authz
|
||||||
else:
|
else:
|
||||||
self.order = Order.create(self.client, self.identifiers)
|
replaces_cert_id = None
|
||||||
|
if (
|
||||||
|
self.include_renewal_cert_id == 'always' or
|
||||||
|
(self.include_renewal_cert_id == 'when_ari_supported' and self.client.directory.has_renewal_info_endpoint())
|
||||||
|
):
|
||||||
|
cert_info = self._get_cert_info_or_none()
|
||||||
|
if cert_info is not None:
|
||||||
|
replaces_cert_id = compute_cert_id(
|
||||||
|
self.client.backend,
|
||||||
|
cert_info=cert_info,
|
||||||
|
none_if_required_information_is_missing=True,
|
||||||
|
)
|
||||||
|
self.order = Order.create(self.client, self.identifiers, replaces_cert_id)
|
||||||
self.order_uri = self.order.url
|
self.order_uri = self.order.url
|
||||||
self.order.load_authorizations(self.client)
|
self.order.load_authorizations(self.client)
|
||||||
self.authorizations.update(self.order.authorizations)
|
self.authorizations.update(self.order.authorizations)
|
||||||
@@ -692,7 +756,7 @@ class ACMECertificateClient(object):
|
|||||||
continue
|
continue
|
||||||
# We drop the type from the key to preserve backwards compatibility
|
# We drop the type from the key to preserve backwards compatibility
|
||||||
data[identifier] = authz.get_challenge_data(self.client)
|
data[identifier] = authz.get_challenge_data(self.client)
|
||||||
if first_step and self.challenge not in data[identifier]:
|
if first_step and self.challenge is not None and self.challenge not in data[identifier]:
|
||||||
raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}!".format(
|
raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}!".format(
|
||||||
self.challenge, type_identifier))
|
self.challenge, type_identifier))
|
||||||
# Get DNS challenge data
|
# Get DNS challenge data
|
||||||
@@ -728,12 +792,23 @@ class ACMECertificateClient(object):
|
|||||||
self.authorizations.update(self.order.authorizations)
|
self.authorizations.update(self.order.authorizations)
|
||||||
|
|
||||||
# Step 2: validate pending challenges
|
# Step 2: validate pending challenges
|
||||||
|
authzs_to_wait_for = []
|
||||||
for type_identifier, authz in self.authorizations.items():
|
for type_identifier, authz in self.authorizations.items():
|
||||||
if authz.status == 'pending':
|
if authz.status == 'pending':
|
||||||
identifier_type, identifier = split_identifier(type_identifier)
|
if self.challenge is not None:
|
||||||
authz.call_validate(self.client, self.challenge)
|
authz.call_validate(self.client, self.challenge, wait=False)
|
||||||
|
authzs_to_wait_for.append(authz)
|
||||||
|
# If there is no challenge, we must check whether the authz is valid
|
||||||
|
elif authz.status != 'valid':
|
||||||
|
authz.raise_error(
|
||||||
|
'Status is not "valid", even though no challenge should be necessary',
|
||||||
|
module=self.client.module,
|
||||||
|
)
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
|
# Step 3: wait for authzs to validate
|
||||||
|
wait_for_validation(authzs_to_wait_for, self.client)
|
||||||
|
|
||||||
def download_alternate_chains(self, cert):
|
def download_alternate_chains(self, cert):
|
||||||
alternate_chains = []
|
alternate_chains = []
|
||||||
for alternate in cert.alternates:
|
for alternate in cert.alternates:
|
||||||
@@ -822,15 +897,14 @@ class ACMECertificateClient(object):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = get_default_argspec()
|
argument_spec = create_default_argspec(with_certificate=True)
|
||||||
argument_spec.update(dict(
|
argument_spec.argument_spec['csr']['aliases'] = ['src']
|
||||||
|
argument_spec.update_argspec(
|
||||||
modify_account=dict(type='bool', default=True),
|
modify_account=dict(type='bool', default=True),
|
||||||
account_email=dict(type='str'),
|
account_email=dict(type='str'),
|
||||||
agreement=dict(type='str'),
|
agreement=dict(type='str'),
|
||||||
terms_agreed=dict(type='bool', default=False),
|
terms_agreed=dict(type='bool', default=False),
|
||||||
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01']),
|
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01', NO_CHALLENGE]),
|
||||||
csr=dict(type='path', aliases=['src']),
|
|
||||||
csr_content=dict(type='str'),
|
|
||||||
data=dict(type='dict'),
|
data=dict(type='dict'),
|
||||||
dest=dict(type='path', aliases=['cert']),
|
dest=dict(type='path', aliases=['cert']),
|
||||||
fullchain_dest=dict(type='path', aliases=['fullchain']),
|
fullchain_dest=dict(type='path', aliases=['fullchain']),
|
||||||
@@ -846,20 +920,14 @@ def main():
|
|||||||
subject_key_identifier=dict(type='str'),
|
subject_key_identifier=dict(type='str'),
|
||||||
authority_key_identifier=dict(type='str'),
|
authority_key_identifier=dict(type='str'),
|
||||||
)),
|
)),
|
||||||
))
|
include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=argument_spec,
|
|
||||||
required_one_of=(
|
|
||||||
['account_key_src', 'account_key_content'],
|
|
||||||
['dest', 'fullchain_dest'],
|
|
||||||
['csr', 'csr_content'],
|
|
||||||
),
|
|
||||||
mutually_exclusive=(
|
|
||||||
['account_key_src', 'account_key_content'],
|
|
||||||
['csr', 'csr_content'],
|
|
||||||
),
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
)
|
||||||
|
argument_spec.update(
|
||||||
|
required_one_of=[
|
||||||
|
['dest', 'fullchain_dest'],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
backend = create_backend(module, False)
|
backend = create_backend(module, False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
119
plugins/modules/acme_certificate_deactivate_authz.py
Normal file
119
plugins/modules/acme_certificate_deactivate_authz.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: acme_certificate_deactivate_authz
|
||||||
|
author: "Felix Fontein (@felixfontein)"
|
||||||
|
version_added: 2.20.0
|
||||||
|
short_description: Deactivate all authz for an ACME v2 order
|
||||||
|
description:
|
||||||
|
- "Deactivate all authentication objects (authz) for an ACME v2 order,
|
||||||
|
which effectively deactivates (invalidates) the order itself."
|
||||||
|
- "Authentication objects are bound to an account key and remain valid
|
||||||
|
for a certain amount of time, and can be used to issue certificates
|
||||||
|
without having to re-authenticate the domain. This can be a security
|
||||||
|
concern."
|
||||||
|
- "Another reason to use this module is to deactivate an order whose
|
||||||
|
processing failed when using O(community.crypto.acme_certificate#module:include_renewal_cert_id)."
|
||||||
|
seealso:
|
||||||
|
- module: community.crypto.acme_certificate
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.account
|
||||||
|
- community.crypto.attributes
|
||||||
|
- community.crypto.attributes.actiongroup_acme
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: none
|
||||||
|
options:
|
||||||
|
order_uri:
|
||||||
|
description:
|
||||||
|
- The ACME v2 order to deactivate.
|
||||||
|
- Can be obtained from RV(community.crypto.acme_certificate#module:order_uri).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Deactivate all authzs for an order
|
||||||
|
community.crypto.acme_certificate_deactivate_authz:
|
||||||
|
account_key_content: "{{ account_private_key }}"
|
||||||
|
order_uri: "{{ certificate_result.order_uri }}"
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''#'''
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
|
create_backend,
|
||||||
|
create_default_argspec,
|
||||||
|
ACMEClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.account import (
|
||||||
|
ACMEAccount,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
||||||
|
ModuleFailException,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.orders import (
|
||||||
|
Order,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = create_default_argspec()
|
||||||
|
argument_spec.update_argspec(
|
||||||
|
order_uri=dict(type='str', required=True),
|
||||||
|
)
|
||||||
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
|
if module.params['acme_version'] == 1:
|
||||||
|
module.fail_json('The module does not support acme_version=1')
|
||||||
|
|
||||||
|
backend = create_backend(module, False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = ACMEClient(module, backend)
|
||||||
|
account = ACMEAccount(client)
|
||||||
|
|
||||||
|
dummy, account_data = account.setup_account(allow_creation=False)
|
||||||
|
if account_data is None:
|
||||||
|
raise ModuleFailException(msg='Account does not exist or is deactivated.')
|
||||||
|
|
||||||
|
order = Order.from_url(client, module.params['order_uri'])
|
||||||
|
order.load_authorizations(client)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
for authz in order.authorizations.values():
|
||||||
|
if not authz.can_deactivate():
|
||||||
|
continue
|
||||||
|
changed = True
|
||||||
|
if module.check_mode:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
authz.deactivate(client)
|
||||||
|
except Exception:
|
||||||
|
# ignore errors
|
||||||
|
pass
|
||||||
|
if authz.status != 'deactivated':
|
||||||
|
module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url))
|
||||||
|
|
||||||
|
module.exit_json(changed=changed)
|
||||||
|
except ModuleFailException as e:
|
||||||
|
e.do_fail(module)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
245
plugins/modules/acme_certificate_renewal_info.py
Normal file
245
plugins/modules/acme_certificate_renewal_info.py
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2018 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 = '''
|
||||||
|
---
|
||||||
|
module: acme_certificate_renewal_info
|
||||||
|
author: "Felix Fontein (@felixfontein)"
|
||||||
|
version_added: 2.20.0
|
||||||
|
short_description: Determine whether a certificate should be renewed or not
|
||||||
|
description:
|
||||||
|
- Uses various information to determine whether a certificate should be renewed or not.
|
||||||
|
- If available, the ARI extension (ACME Renewal Information, U(https://datatracker.ietf.org/doc/draft-ietf-acme-ari/))
|
||||||
|
is used. This module implements version 3 of the ARI draft."
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.no_account
|
||||||
|
- community.crypto.attributes
|
||||||
|
- community.crypto.attributes.info_module
|
||||||
|
options:
|
||||||
|
certificate_path:
|
||||||
|
description:
|
||||||
|
- A path to the X.509 certificate to determine renewal of.
|
||||||
|
- In case the certificate does not exist, the module will always return RV(should_renew=true).
|
||||||
|
- O(certificate_path) and O(certificate_content) are mutually exclusive.
|
||||||
|
type: path
|
||||||
|
certificate_content:
|
||||||
|
description:
|
||||||
|
- The content of the X.509 certificate to determine renewal of.
|
||||||
|
- O(certificate_path) and O(certificate_content) are mutually exclusive.
|
||||||
|
type: str
|
||||||
|
use_ari:
|
||||||
|
description:
|
||||||
|
- Whether to use ARI information, if available.
|
||||||
|
- Set this to V(false) if the ACME server implements ARI in a way that is incompatible with this module.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
ari_algorithm:
|
||||||
|
description:
|
||||||
|
- If ARI information is used, selects which algorithm is used to determine whether to renew now.
|
||||||
|
- V(standard) selects the L(algorithm provided in the the ARI specification,
|
||||||
|
https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#name-renewalinfo-objects).
|
||||||
|
- V(start) returns RV(should_renew=true) once the start of the renewal interval has been reached.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- standard
|
||||||
|
- start
|
||||||
|
default: standard
|
||||||
|
remaining_days:
|
||||||
|
description:
|
||||||
|
- The number of days the certificate must have left being valid.
|
||||||
|
- For example, if O(remaining_days=20), this check causes RV(should_renew=true) if the
|
||||||
|
certificate is valid for less than 20 days.
|
||||||
|
type: int
|
||||||
|
remaining_percentage:
|
||||||
|
description:
|
||||||
|
- The percentage of the certificate's validity period that should be left.
|
||||||
|
- For example, if O(remaining_percentage=0.1), and the certificate's validity period is 90 days,
|
||||||
|
this check causes RV(should_renew=true) if the certificate is valid for less than 9 days.
|
||||||
|
- Must be a value between 0 and 1.
|
||||||
|
type: float
|
||||||
|
now:
|
||||||
|
description:
|
||||||
|
- Use this timestamp instead of the current timestamp to determine whether a certificate should be renewed.
|
||||||
|
- 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]) (for example V(+32w1d2h)).
|
||||||
|
type: str
|
||||||
|
seealso:
|
||||||
|
- module: community.crypto.acme_certificate
|
||||||
|
description: Allows to obtain a certificate using the ACME protocol
|
||||||
|
- module: community.crypto.acme_ari_info
|
||||||
|
description: Obtain renewal information for a certificate
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Retrieve renewal information for a certificate
|
||||||
|
community.crypto.acme_certificate_renewal_info:
|
||||||
|
certificate_path: /etc/httpd/ssl/sample.com.crt
|
||||||
|
register: cert_data
|
||||||
|
|
||||||
|
- name: Should the certificate be renewed?
|
||||||
|
ansible.builtin.debug:
|
||||||
|
var: cert_data.should_renew
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
should_renew:
|
||||||
|
description:
|
||||||
|
- Whether the certificate should be renewed.
|
||||||
|
- If no certificate is provided, or the certificate is expired, will always be V(true).
|
||||||
|
returned: success
|
||||||
|
type: bool
|
||||||
|
sample: true
|
||||||
|
|
||||||
|
msg:
|
||||||
|
description:
|
||||||
|
- Information on the reason for renewal.
|
||||||
|
- Should be shown to the user, as in case of ARI triggered renewal it can contain important
|
||||||
|
information, for example on forced revocations for misissued certificates.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: The certificate does not exist.
|
||||||
|
|
||||||
|
supports_ari:
|
||||||
|
description:
|
||||||
|
- Whether ARI information was used to determine renewal. This can be used to determine whether to
|
||||||
|
specify O(community.crypto.acme_certificate#module:include_renewal_cert_id=when_ari_supported)
|
||||||
|
for the M(community.crypto.acme_certificate) module.
|
||||||
|
- If O(use_ari=false), this will always be V(false).
|
||||||
|
returned: success
|
||||||
|
type: bool
|
||||||
|
sample: true
|
||||||
|
|
||||||
|
cert_id:
|
||||||
|
description:
|
||||||
|
- The certificate ID according to the L(ARI specification, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.1).
|
||||||
|
returned: success, the certificate exists, and has an Authority Key Identifier X.509 extension
|
||||||
|
type: str
|
||||||
|
sample: aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
|
create_backend,
|
||||||
|
create_default_argspec,
|
||||||
|
ACMEClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import compute_cert_id
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = create_default_argspec(with_account=False)
|
||||||
|
argument_spec.update_argspec(
|
||||||
|
certificate_path=dict(type='path'),
|
||||||
|
certificate_content=dict(type='str'),
|
||||||
|
use_ari=dict(type='bool', default=True),
|
||||||
|
ari_algorithm=dict(type='str', choices=['standard', 'start'], default='standard'),
|
||||||
|
remaining_days=dict(type='int'),
|
||||||
|
remaining_percentage=dict(type='float'),
|
||||||
|
now=dict(type='str'),
|
||||||
|
)
|
||||||
|
argument_spec.update(
|
||||||
|
mutually_exclusive=(
|
||||||
|
['certificate_path', 'certificate_content'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
|
backend = create_backend(module, True)
|
||||||
|
|
||||||
|
result = dict(
|
||||||
|
changed=False,
|
||||||
|
msg='The certificate is still valid and no condition was reached',
|
||||||
|
supports_ari=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def complete(should_renew, **kwargs):
|
||||||
|
result['should_renew'] = should_renew
|
||||||
|
result.update(kwargs)
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
if not module.params['certificate_path'] and not module.params['certificate_content']:
|
||||||
|
complete(True, msg='No certificate was specified')
|
||||||
|
|
||||||
|
if module.params['certificate_path'] is not None and not os.path.exists(module.params['certificate_path']):
|
||||||
|
complete(True, msg='The certificate file does not exist')
|
||||||
|
|
||||||
|
try:
|
||||||
|
cert_info = backend.get_cert_information(
|
||||||
|
cert_filename=module.params['certificate_path'],
|
||||||
|
cert_content=module.params['certificate_content'],
|
||||||
|
)
|
||||||
|
cert_id = compute_cert_id(backend, cert_info=cert_info, none_if_required_information_is_missing=True)
|
||||||
|
if cert_id is not None:
|
||||||
|
result['cert_id'] = cert_id
|
||||||
|
|
||||||
|
if module.params['now']:
|
||||||
|
now = backend.parse_module_parameter(module.params['now'], 'now')
|
||||||
|
else:
|
||||||
|
now = backend.get_now()
|
||||||
|
|
||||||
|
if now >= cert_info.not_valid_after:
|
||||||
|
complete(True, msg='The certificate has already expired')
|
||||||
|
|
||||||
|
client = ACMEClient(module, backend)
|
||||||
|
if cert_id is not None and module.params['use_ari'] and client.directory.has_renewal_info_endpoint():
|
||||||
|
renewal_info = client.get_renewal_info(cert_id=cert_id)
|
||||||
|
window_start = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['start'])
|
||||||
|
window_end = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['end'])
|
||||||
|
msg_append = ''
|
||||||
|
if 'explanationURL' in renewal_info:
|
||||||
|
msg_append = '. Information on renewal interval: {0}'.format(renewal_info['explanationURL'])
|
||||||
|
result['supports_ari'] = True
|
||||||
|
if now > window_end:
|
||||||
|
complete(True, msg='The suggested renewal interval provided by ARI is in the past{0}'.format(msg_append))
|
||||||
|
if module.params['ari_algorithm'] == 'start':
|
||||||
|
if now > window_start:
|
||||||
|
complete(True, msg='The suggested renewal interval provided by ARI has begun{0}'.format(msg_append))
|
||||||
|
else:
|
||||||
|
random_time = backend.interpolate_timestamp(window_start, window_end, random.random())
|
||||||
|
if now > random_time:
|
||||||
|
complete(
|
||||||
|
True,
|
||||||
|
msg='The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}'.format(
|
||||||
|
random_time,
|
||||||
|
msg_append,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if module.params['remaining_days'] is not None:
|
||||||
|
remaining_days = (cert_info.not_valid_after - now).days
|
||||||
|
if remaining_days < module.params['remaining_days']:
|
||||||
|
complete(True, msg='The certificate expires in {0} days'.format(remaining_days))
|
||||||
|
|
||||||
|
if module.params['remaining_percentage'] is not None:
|
||||||
|
timestamp = backend.interpolate_timestamp(cert_info.not_valid_before, cert_info.not_valid_after, 1 - module.params['remaining_percentage'])
|
||||||
|
if timestamp < now:
|
||||||
|
complete(
|
||||||
|
True,
|
||||||
|
msg="The remaining percentage {0}% of the certificate's lifespan was reached on {1}".format(
|
||||||
|
module.params['remaining_percentage'] * 100,
|
||||||
|
timestamp,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
complete(False)
|
||||||
|
except ModuleFailException as e:
|
||||||
|
e.do_fail(module)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -19,8 +19,8 @@ description:
|
|||||||
L(ACME protocol,https://tools.ietf.org/html/rfc8555),
|
L(ACME protocol,https://tools.ietf.org/html/rfc8555),
|
||||||
such as L(Let's Encrypt,https://letsencrypt.org/)."
|
such as L(Let's Encrypt,https://letsencrypt.org/)."
|
||||||
notes:
|
notes:
|
||||||
- "Exactly one of C(account_key_src), C(account_key_content),
|
- "Exactly one of O(account_key_src), O(account_key_content),
|
||||||
C(private_key_src) or C(private_key_content) must be specified."
|
O(private_key_src), or O(private_key_content) must be specified."
|
||||||
- "Trying to revoke an already revoked certificate
|
- "Trying to revoke an already revoked certificate
|
||||||
should result in an unchanged status, even if the revocation reason
|
should result in an unchanged status, even if the revocation reason
|
||||||
was different than the one specified here. Also, depending on the
|
was different than the one specified here. Also, depending on the
|
||||||
@@ -37,7 +37,8 @@ seealso:
|
|||||||
- module: community.crypto.acme_inspect
|
- module: community.crypto.acme_inspect
|
||||||
description: Allows to debug problems.
|
description: Allows to debug problems.
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.acme
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.account
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
- community.crypto.attributes.actiongroup_acme
|
- community.crypto.attributes.actiongroup_acme
|
||||||
attributes:
|
attributes:
|
||||||
@@ -58,13 +59,13 @@ options:
|
|||||||
- "RSA keys can be created with C(openssl rsa ...). Elliptic curve keys can
|
- "RSA keys can be created with C(openssl rsa ...). Elliptic curve keys can
|
||||||
be created with C(openssl ecparam -genkey ...). Any other tool creating
|
be created with C(openssl ecparam -genkey ...). Any other tool creating
|
||||||
private keys in PEM format can be used as well."
|
private keys in PEM format can be used as well."
|
||||||
- "Mutually exclusive with C(account_key_content)."
|
- "Mutually exclusive with O(account_key_content)."
|
||||||
- "Required if C(account_key_content) is not used."
|
- "Required if O(account_key_content) is not used."
|
||||||
account_key_content:
|
account_key_content:
|
||||||
description:
|
description:
|
||||||
- "Content of the ACME account RSA or Elliptic Curve key."
|
- "Content of the ACME account RSA or Elliptic Curve key."
|
||||||
- "Note that exactly one of C(account_key_src), C(account_key_content),
|
- "Note that exactly one of O(account_key_src), O(account_key_content),
|
||||||
C(private_key_src) or C(private_key_content) must be specified."
|
O(private_key_src), or O(private_key_content) must be specified."
|
||||||
- "I(Warning): the content will be written into a temporary file, which will
|
- "I(Warning): the content will be written into a temporary file, which will
|
||||||
be deleted by Ansible when the module completes. Since this is an
|
be deleted by Ansible when the module completes. Since this is an
|
||||||
important private key — it can be used to change the account key,
|
important private key — it can be used to change the account key,
|
||||||
@@ -77,14 +78,14 @@ options:
|
|||||||
private_key_src:
|
private_key_src:
|
||||||
description:
|
description:
|
||||||
- "Path to the certificate's private key."
|
- "Path to the certificate's private key."
|
||||||
- "Note that exactly one of C(account_key_src), C(account_key_content),
|
- "Note that exactly one of O(account_key_src), O(account_key_content),
|
||||||
C(private_key_src) or C(private_key_content) must be specified."
|
O(private_key_src), or O(private_key_content) must be specified."
|
||||||
type: path
|
type: path
|
||||||
private_key_content:
|
private_key_content:
|
||||||
description:
|
description:
|
||||||
- "Content of the certificate's private key."
|
- "Content of the certificate's private key."
|
||||||
- "Note that exactly one of C(account_key_src), C(account_key_content),
|
- "Note that exactly one of O(account_key_src), O(account_key_content),
|
||||||
C(private_key_src) or C(private_key_content) must be specified."
|
O(private_key_src), or O(private_key_content) must be specified."
|
||||||
- "I(Warning): the content will be written into a temporary file, which will
|
- "I(Warning): the content will be written into a temporary file, which will
|
||||||
be deleted by Ansible when the module completes. Since this is an
|
be deleted by Ansible when the module completes. Since this is an
|
||||||
important private key — it can be used to change the account key,
|
important private key — it can be used to change the account key,
|
||||||
@@ -105,11 +106,11 @@ options:
|
|||||||
description:
|
description:
|
||||||
- "One of the revocation reasonCodes defined in
|
- "One of the revocation reasonCodes defined in
|
||||||
L(Section 5.3.1 of RFC5280,https://tools.ietf.org/html/rfc5280#section-5.3.1)."
|
L(Section 5.3.1 of RFC5280,https://tools.ietf.org/html/rfc5280#section-5.3.1)."
|
||||||
- "Possible values are C(0) (unspecified), C(1) (keyCompromise),
|
- "Possible values are V(0) (unspecified), V(1) (keyCompromise),
|
||||||
C(2) (cACompromise), C(3) (affiliationChanged), C(4) (superseded),
|
V(2) (cACompromise), V(3) (affiliationChanged), V(4) (superseded),
|
||||||
C(5) (cessationOfOperation), C(6) (certificateHold),
|
V(5) (cessationOfOperation), V(6) (certificateHold),
|
||||||
C(8) (removeFromCRL), C(9) (privilegeWithdrawn),
|
V(8) (removeFromCRL), V(9) (privilegeWithdrawn),
|
||||||
C(10) (aACompromise)."
|
V(10) (aACompromise)."
|
||||||
type: int
|
type: int
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -127,11 +128,9 @@ EXAMPLES = '''
|
|||||||
|
|
||||||
RETURN = '''#'''
|
RETURN = '''#'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
create_backend,
|
create_backend,
|
||||||
get_default_argspec,
|
create_default_argspec,
|
||||||
ACMEClient,
|
ACMEClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -152,24 +151,23 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = get_default_argspec()
|
argument_spec = create_default_argspec(require_account_key=False)
|
||||||
argument_spec.update(dict(
|
argument_spec.update_argspec(
|
||||||
private_key_src=dict(type='path'),
|
private_key_src=dict(type='path'),
|
||||||
private_key_content=dict(type='str', no_log=True),
|
private_key_content=dict(type='str', no_log=True),
|
||||||
private_key_passphrase=dict(type='str', no_log=True),
|
private_key_passphrase=dict(type='str', no_log=True),
|
||||||
certificate=dict(type='path', required=True),
|
certificate=dict(type='path', required=True),
|
||||||
revoke_reason=dict(type='int'),
|
revoke_reason=dict(type='int'),
|
||||||
))
|
)
|
||||||
module = AnsibleModule(
|
argument_spec.update(
|
||||||
argument_spec=argument_spec,
|
|
||||||
required_one_of=(
|
required_one_of=(
|
||||||
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
|
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
|
||||||
),
|
),
|
||||||
mutually_exclusive=(
|
mutually_exclusive=(
|
||||||
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
|
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
|
||||||
),
|
),
|
||||||
supports_check_mode=False,
|
|
||||||
)
|
)
|
||||||
|
module = argument_spec.create_ansible_module()
|
||||||
backend = create_backend(module, False)
|
backend = create_backend(module, False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ options:
|
|||||||
- tls-alpn-01
|
- tls-alpn-01
|
||||||
challenge_data:
|
challenge_data:
|
||||||
description:
|
description:
|
||||||
- "The C(challenge_data) entry provided by M(community.crypto.acme_certificate) for the
|
- "The RV(community.crypto.acme_certificate#module:challenge_data) entry provided by M(community.crypto.acme_certificate) for the
|
||||||
challenge."
|
challenge."
|
||||||
type: dict
|
type: dict
|
||||||
required: true
|
required: true
|
||||||
@@ -57,12 +57,12 @@ options:
|
|||||||
description:
|
description:
|
||||||
- "Path to a file containing the private key file to use for this challenge
|
- "Path to a file containing the private key file to use for this challenge
|
||||||
certificate."
|
certificate."
|
||||||
- "Mutually exclusive with C(private_key_content)."
|
- "Mutually exclusive with O(private_key_content)."
|
||||||
type: path
|
type: path
|
||||||
private_key_content:
|
private_key_content:
|
||||||
description:
|
description:
|
||||||
- "Content of the private key to use for this challenge certificate."
|
- "Content of the private key to use for this challenge certificate."
|
||||||
- "Mutually exclusive with C(private_key_src)."
|
- "Mutually exclusive with O(private_key_src)."
|
||||||
type: str
|
type: str
|
||||||
private_key_passphrase:
|
private_key_passphrase:
|
||||||
description:
|
description:
|
||||||
@@ -122,14 +122,16 @@ domain:
|
|||||||
type: str
|
type: str
|
||||||
identifier_type:
|
identifier_type:
|
||||||
description:
|
description:
|
||||||
- "The identifier type for the actual resource identifier. Will be C(dns)
|
- "The identifier type for the actual resource identifier."
|
||||||
or C(ip)."
|
|
||||||
returned: always
|
returned: always
|
||||||
type: str
|
type: str
|
||||||
|
choices:
|
||||||
|
- dns
|
||||||
|
- ip
|
||||||
identifier:
|
identifier:
|
||||||
description:
|
description:
|
||||||
- "The identifier for the actual resource. Will be a domain name if the
|
- "The identifier for the actual resource. Will be a domain name if
|
||||||
type is C(dns), or an IP address if the type is C(ip)."
|
RV(identifier_type=dns), or an IP address if RV(identifier_type=ip)."
|
||||||
returned: always
|
returned: always
|
||||||
type: str
|
type: str
|
||||||
challenge_certificate:
|
challenge_certificate:
|
||||||
@@ -163,6 +165,16 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
|
|||||||
read_file,
|
read_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
|
set_not_valid_after,
|
||||||
|
set_not_valid_before,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
get_now_datetime,
|
||||||
|
)
|
||||||
|
|
||||||
CRYPTOGRAPHY_IMP_ERR = None
|
CRYPTOGRAPHY_IMP_ERR = None
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
@@ -242,8 +254,9 @@ def main():
|
|||||||
domain = to_text(challenge_data['resource'])
|
domain = to_text(challenge_data['resource'])
|
||||||
identifier_type, identifier = to_text(challenge_data.get('resource_original', 'dns:' + challenge_data['resource'])).split(':', 1)
|
identifier_type, identifier = to_text(challenge_data.get('resource_original', 'dns:' + challenge_data['resource'])).split(':', 1)
|
||||||
subject = issuer = cryptography.x509.Name([])
|
subject = issuer = cryptography.x509.Name([])
|
||||||
not_valid_before = datetime.datetime.utcnow()
|
now = get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
not_valid_after = datetime.datetime.utcnow() + datetime.timedelta(days=10)
|
not_valid_before = now
|
||||||
|
not_valid_after = now + datetime.timedelta(days=10)
|
||||||
if identifier_type == 'dns':
|
if identifier_type == 'dns':
|
||||||
san = cryptography.x509.DNSName(identifier)
|
san = cryptography.x509.DNSName(identifier)
|
||||||
elif identifier_type == 'ip':
|
elif identifier_type == 'ip':
|
||||||
@@ -252,7 +265,7 @@ def main():
|
|||||||
raise ModuleFailException('Unsupported identifier type "{0}"'.format(identifier_type))
|
raise ModuleFailException('Unsupported identifier type "{0}"'.format(identifier_type))
|
||||||
|
|
||||||
# Generate regular self-signed certificate
|
# Generate regular self-signed certificate
|
||||||
regular_certificate = cryptography.x509.CertificateBuilder().subject_name(
|
cert_builder = cryptography.x509.CertificateBuilder().subject_name(
|
||||||
subject
|
subject
|
||||||
).issuer_name(
|
).issuer_name(
|
||||||
issuer
|
issuer
|
||||||
@@ -260,14 +273,13 @@ def main():
|
|||||||
private_key.public_key()
|
private_key.public_key()
|
||||||
).serial_number(
|
).serial_number(
|
||||||
cryptography.x509.random_serial_number()
|
cryptography.x509.random_serial_number()
|
||||||
).not_valid_before(
|
|
||||||
not_valid_before
|
|
||||||
).not_valid_after(
|
|
||||||
not_valid_after
|
|
||||||
).add_extension(
|
).add_extension(
|
||||||
cryptography.x509.SubjectAlternativeName([san]),
|
cryptography.x509.SubjectAlternativeName([san]),
|
||||||
critical=False,
|
critical=False,
|
||||||
).sign(
|
)
|
||||||
|
cert_builder = set_not_valid_before(cert_builder, not_valid_before)
|
||||||
|
cert_builder = set_not_valid_after(cert_builder, not_valid_after)
|
||||||
|
regular_certificate = cert_builder.sign(
|
||||||
private_key,
|
private_key,
|
||||||
cryptography.hazmat.primitives.hashes.SHA256(),
|
cryptography.hazmat.primitives.hashes.SHA256(),
|
||||||
_cryptography_backend
|
_cryptography_backend
|
||||||
@@ -276,7 +288,7 @@ def main():
|
|||||||
# Process challenge
|
# Process challenge
|
||||||
if challenge == 'tls-alpn-01':
|
if challenge == 'tls-alpn-01':
|
||||||
value = base64.b64decode(challenge_data['resource_value'])
|
value = base64.b64decode(challenge_data['resource_value'])
|
||||||
challenge_certificate = cryptography.x509.CertificateBuilder().subject_name(
|
cert_builder = cryptography.x509.CertificateBuilder().subject_name(
|
||||||
subject
|
subject
|
||||||
).issuer_name(
|
).issuer_name(
|
||||||
issuer
|
issuer
|
||||||
@@ -284,10 +296,6 @@ def main():
|
|||||||
private_key.public_key()
|
private_key.public_key()
|
||||||
).serial_number(
|
).serial_number(
|
||||||
cryptography.x509.random_serial_number()
|
cryptography.x509.random_serial_number()
|
||||||
).not_valid_before(
|
|
||||||
not_valid_before
|
|
||||||
).not_valid_after(
|
|
||||||
not_valid_after
|
|
||||||
).add_extension(
|
).add_extension(
|
||||||
cryptography.x509.SubjectAlternativeName([san]),
|
cryptography.x509.SubjectAlternativeName([san]),
|
||||||
critical=False,
|
critical=False,
|
||||||
@@ -297,7 +305,10 @@ def main():
|
|||||||
encode_octet_string(value),
|
encode_octet_string(value),
|
||||||
),
|
),
|
||||||
critical=True,
|
critical=True,
|
||||||
).sign(
|
)
|
||||||
|
cert_builder = set_not_valid_before(cert_builder, not_valid_before)
|
||||||
|
cert_builder = set_not_valid_after(cert_builder, not_valid_after)
|
||||||
|
challenge_certificate = cert_builder.sign(
|
||||||
private_key,
|
private_key,
|
||||||
cryptography.hazmat.primitives.hashes.SHA256(),
|
cryptography.hazmat.primitives.hashes.SHA256(),
|
||||||
_cryptography_backend
|
_cryptography_backend
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ description:
|
|||||||
- "The module can also be used to directly access features of an ACME servers
|
- "The module can also be used to directly access features of an ACME servers
|
||||||
which are not yet supported by the Ansible ACME modules."
|
which are not yet supported by the Ansible ACME modules."
|
||||||
notes:
|
notes:
|
||||||
- "The I(account_uri) option must be specified for properly authenticated
|
- "The O(account_uri) option must be specified for properly authenticated
|
||||||
ACME v2 requests (except a C(new-account) request)."
|
ACME v2 requests (except a C(new-account) request)."
|
||||||
- "Using the C(ansible) tool, M(community.crypto.acme_inspect) can be used to directly execute
|
- "Using the C(ansible) tool, M(community.crypto.acme_inspect) can be used to directly execute
|
||||||
ACME requests without the need of writing a playbook. For example, the
|
ACME requests without the need of writing a playbook. For example, the
|
||||||
@@ -42,7 +42,8 @@ seealso:
|
|||||||
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
|
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
|
||||||
link: https://www.rfc-editor.org/rfc/rfc8737.html
|
link: https://www.rfc-editor.org/rfc/rfc8737.html
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.acme
|
- community.crypto.acme.basic
|
||||||
|
- community.crypto.acme.account
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
- community.crypto.attributes.actiongroup_acme
|
- community.crypto.attributes.actiongroup_acme
|
||||||
attributes:
|
attributes:
|
||||||
@@ -54,16 +55,16 @@ options:
|
|||||||
url:
|
url:
|
||||||
description:
|
description:
|
||||||
- "The URL to send the request to."
|
- "The URL to send the request to."
|
||||||
- "Must be specified if I(method) is not C(directory-only)."
|
- "Must be specified if O(method) is not V(directory-only)."
|
||||||
type: str
|
type: str
|
||||||
method:
|
method:
|
||||||
description:
|
description:
|
||||||
- "The method to use to access the given URL on the ACME server."
|
- "The method to use to access the given URL on the ACME server."
|
||||||
- "The value C(post) executes an authenticated POST request. The content
|
- "The value V(post) executes an authenticated POST request. The content
|
||||||
must be specified in the I(content) option."
|
must be specified in the O(content) option."
|
||||||
- "The value C(get) executes an authenticated POST-as-GET request for ACME v2,
|
- "The value V(get) executes an authenticated POST-as-GET request for ACME v2,
|
||||||
and a regular GET request for ACME v1."
|
and a regular GET request for ACME v1."
|
||||||
- "The value C(directory-only) only retrieves the directory, without doing
|
- "The value V(directory-only) only retrieves the directory, without doing
|
||||||
a request."
|
a request."
|
||||||
type: str
|
type: str
|
||||||
default: get
|
default: get
|
||||||
@@ -73,13 +74,13 @@ options:
|
|||||||
- directory-only
|
- directory-only
|
||||||
content:
|
content:
|
||||||
description:
|
description:
|
||||||
- "An encoded JSON object which will be sent as the content if I(method)
|
- "An encoded JSON object which will be sent as the content if O(method)
|
||||||
is C(post)."
|
is V(post)."
|
||||||
- "Required when I(method) is C(post), and not allowed otherwise."
|
- "Required when O(method) is V(post), and not allowed otherwise."
|
||||||
type: str
|
type: str
|
||||||
fail_on_acme_error:
|
fail_on_acme_error:
|
||||||
description:
|
description:
|
||||||
- "If I(method) is C(post) or C(get), make the module fail in case an ACME
|
- "If O(method) is V(post) or V(get), make the module fail in case an ACME
|
||||||
error is returned."
|
error is returned."
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
@@ -247,12 +248,11 @@ output_json:
|
|||||||
- ...
|
- ...
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
|
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||||
create_backend,
|
create_backend,
|
||||||
get_default_argspec,
|
create_default_argspec,
|
||||||
ACMEClient,
|
ACMEClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -263,18 +263,14 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = get_default_argspec()
|
argument_spec = create_default_argspec(require_account_key=False)
|
||||||
argument_spec.update(dict(
|
argument_spec.update_argspec(
|
||||||
url=dict(type='str'),
|
url=dict(type='str'),
|
||||||
method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'),
|
method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'),
|
||||||
content=dict(type='str'),
|
content=dict(type='str'),
|
||||||
fail_on_acme_error=dict(type='bool', default=True),
|
fail_on_acme_error=dict(type='bool', default=True),
|
||||||
))
|
)
|
||||||
module = AnsibleModule(
|
argument_spec.update(
|
||||||
argument_spec=argument_spec,
|
|
||||||
mutually_exclusive=(
|
|
||||||
['account_key_src', 'account_key_content'],
|
|
||||||
),
|
|
||||||
required_if=(
|
required_if=(
|
||||||
['method', 'get', ['url']],
|
['method', 'get', ['url']],
|
||||||
['method', 'post', ['url', 'content']],
|
['method', 'post', ['url', 'content']],
|
||||||
@@ -282,6 +278,7 @@ def main():
|
|||||||
['method', 'post', ['account_key_src', 'account_key_content'], True],
|
['method', 'post', ['account_key_src', 'account_key_content'], True],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
module = argument_spec.create_ansible_module()
|
||||||
backend = create_backend(module, False)
|
backend = create_backend(module, False)
|
||||||
|
|
||||||
result = dict()
|
result = dict()
|
||||||
|
|||||||
@@ -78,12 +78,12 @@ EXAMPLES = '''
|
|||||||
# certificates, finds the associated root certificate.
|
# certificates, finds the associated root certificate.
|
||||||
- name: Find root certificate
|
- name: Find root certificate
|
||||||
community.crypto.certificate_complete_chain:
|
community.crypto.certificate_complete_chain:
|
||||||
input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com-fullchain.pem') }}"
|
input_chain: "{{ lookup('ansible.builtin.file', '/etc/ssl/csr/www.ansible.com-fullchain.pem') }}"
|
||||||
root_certificates:
|
root_certificates:
|
||||||
- /etc/ca-certificates/
|
- /etc/ca-certificates/
|
||||||
register: www_ansible_com
|
register: www_ansible_com
|
||||||
- name: Write root certificate to disk
|
- name: Write root certificate to disk
|
||||||
copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/ssl/csr/www.ansible.com-root.pem
|
dest: /etc/ssl/csr/www.ansible.com-root.pem
|
||||||
content: "{{ www_ansible_com.root }}"
|
content: "{{ www_ansible_com.root }}"
|
||||||
|
|
||||||
@@ -91,18 +91,18 @@ EXAMPLES = '''
|
|||||||
# certificates, finds the associated root certificate.
|
# certificates, finds the associated root certificate.
|
||||||
- name: Find root certificate
|
- name: Find root certificate
|
||||||
community.crypto.certificate_complete_chain:
|
community.crypto.certificate_complete_chain:
|
||||||
input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.pem') }}"
|
input_chain: "{{ lookup('ansible.builtin.file', '/etc/ssl/csr/www.ansible.com.pem') }}"
|
||||||
intermediate_certificates:
|
intermediate_certificates:
|
||||||
- /etc/ssl/csr/www.ansible.com-chain.pem
|
- /etc/ssl/csr/www.ansible.com-chain.pem
|
||||||
root_certificates:
|
root_certificates:
|
||||||
- /etc/ca-certificates/
|
- /etc/ca-certificates/
|
||||||
register: www_ansible_com
|
register: www_ansible_com
|
||||||
- name: Write complete chain to disk
|
- name: Write complete chain to disk
|
||||||
copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/ssl/csr/www.ansible.com-completechain.pem
|
dest: /etc/ssl/csr/www.ansible.com-completechain.pem
|
||||||
content: "{{ ''.join(www_ansible_com.complete_chain) }}"
|
content: "{{ ''.join(www_ansible_com.complete_chain) }}"
|
||||||
- name: Write root chain (intermediates and root) to disk
|
- name: Write root chain (intermediates and root) to disk
|
||||||
copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/ssl/csr/www.ansible.com-rootchain.pem
|
dest: /etc/ssl/csr/www.ansible.com-rootchain.pem
|
||||||
content: "{{ ''.join(www_ansible_com.chain) }}"
|
content: "{{ ''.join(www_ansible_com.chain) }}"
|
||||||
'''
|
'''
|
||||||
@@ -142,6 +142,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
|
|||||||
split_pem_list,
|
split_pem_list,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
|
CRYPTOGRAPHY_HAS_ED448_SIGN,
|
||||||
|
CRYPTOGRAPHY_HAS_ED25519_SIGN,
|
||||||
|
)
|
||||||
|
|
||||||
CRYPTOGRAPHY_IMP_ERR = None
|
CRYPTOGRAPHY_IMP_ERR = None
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
@@ -196,6 +201,12 @@ def is_parent(module, cert, potential_parent):
|
|||||||
cert.cert.tbs_certificate_bytes,
|
cert.cert.tbs_certificate_bytes,
|
||||||
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cert.cert.signature_hash_algorithm),
|
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cert.cert.signature_hash_algorithm),
|
||||||
)
|
)
|
||||||
|
elif CRYPTOGRAPHY_HAS_ED25519_SIGN and isinstance(
|
||||||
|
public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
|
||||||
|
public_key.verify(cert.cert.signature, cert.cert.tbs_certificate_bytes)
|
||||||
|
elif CRYPTOGRAPHY_HAS_ED448_SIGN and isinstance(
|
||||||
|
public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
|
||||||
|
public_key.verify(cert.cert.signature, cert.cert.tbs_certificate_bytes)
|
||||||
else:
|
else:
|
||||||
# Unknown public key type
|
# Unknown public key type
|
||||||
module.warn('Unknown public key type "{0}"'.format(public_key))
|
module.warn('Unknown public key type "{0}"'.format(public_key))
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ python_cryptography_installed:
|
|||||||
|
|
||||||
python_cryptography_import_error:
|
python_cryptography_import_error:
|
||||||
description: Import error when trying to import the L(Python cryptography library, https://cryptography.io/).
|
description: Import error when trying to import the L(Python cryptography library, https://cryptography.io/).
|
||||||
returned: when I(python_cryptography_installed=false)
|
returned: when RV(python_cryptography_installed=false)
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
python_cryptography_capabilities:
|
python_cryptography_capabilities:
|
||||||
description: Information on the installed L(Python cryptography library, https://cryptography.io/).
|
description: Information on the installed L(Python cryptography library, https://cryptography.io/).
|
||||||
returned: when I(python_cryptography_installed=true)
|
returned: when RV(python_cryptography_installed=true)
|
||||||
type: dict
|
type: dict
|
||||||
contains:
|
contains:
|
||||||
version:
|
version:
|
||||||
@@ -136,7 +136,7 @@ openssl_present:
|
|||||||
|
|
||||||
openssl:
|
openssl:
|
||||||
description: Information on the installed OpenSSL binary.
|
description: Information on the installed OpenSSL binary.
|
||||||
returned: when I(openssl_present=true)
|
returned: when RV(openssl_present=true)
|
||||||
type: dict
|
type: dict
|
||||||
contains:
|
contains:
|
||||||
path:
|
path:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ description:
|
|||||||
- In order to request a certificate, the domain and organization used in the certificate signing request must be already
|
- In order to request a certificate, the domain and organization used in the certificate signing request must be already
|
||||||
validated in the ECS system. It is I(not) the responsibility of this module to perform those steps.
|
validated in the ECS system. It is I(not) the responsibility of this module to perform those steps.
|
||||||
notes:
|
notes:
|
||||||
- C(path) must be specified as the output location of the certificate.
|
- O(path) must be specified as the output location of the certificate.
|
||||||
requirements:
|
requirements:
|
||||||
- cryptography >= 1.6
|
- cryptography >= 1.6
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
@@ -32,7 +32,7 @@ attributes:
|
|||||||
check_mode:
|
check_mode:
|
||||||
support: partial
|
support: partial
|
||||||
details:
|
details:
|
||||||
- Check mode is only supported if I(request_type=new).
|
- Check mode is only supported if O(request_type=new).
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: none
|
support: none
|
||||||
safe_file_operations:
|
safe_file_operations:
|
||||||
@@ -40,14 +40,14 @@ attributes:
|
|||||||
options:
|
options:
|
||||||
backup:
|
backup:
|
||||||
description:
|
description:
|
||||||
- Whether a backup should be made for the certificate in I(path).
|
- Whether a backup should be made for the certificate in O(path).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
force:
|
force:
|
||||||
description:
|
description:
|
||||||
- If force is used, a certificate is requested regardless of whether I(path) points to an existing valid certificate.
|
- If force is used, a certificate is requested regardless of whether O(path) points to an existing valid certificate.
|
||||||
- If C(request_type=renew), a forced renew will fail if the certificate being renewed has been issued within the past 30 days, regardless of the
|
- If O(request_type=renew), a forced renew will fail if the certificate being renewed has been issued within the past 30 days, regardless of the
|
||||||
value of I(remaining_days) or the return value of I(cert_days) - the ECS API does not support the "renew" operation for certificates that are not
|
value of O(remaining_days) or the return value of RV(cert_days) - the ECS API does not support the "renew" operation for certificates that are not
|
||||||
at least 30 days old.
|
at least 30 days old.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
@@ -56,9 +56,9 @@ options:
|
|||||||
- The destination path for the generated certificate as a PEM encoded cert.
|
- The destination path for the generated certificate as a PEM encoded cert.
|
||||||
- If the certificate at this location is not an Entrust issued certificate, a new certificate will always be requested even if the current
|
- If the certificate at this location is not an Entrust issued certificate, a new certificate will always be requested even if the current
|
||||||
certificate is technically valid.
|
certificate is technically valid.
|
||||||
- If there is already an Entrust certificate at this location, whether it is replaced is depends on the I(remaining_days) calculation.
|
- If there is already an Entrust certificate at this location, whether it is replaced is depends on the O(remaining_days) calculation.
|
||||||
- If an existing certificate is being replaced (see I(remaining_days), I(force), and I(tracking_id)), whether a new certificate is requested
|
- If an existing certificate is being replaced (see O(remaining_days), O(force), and O(tracking_id)), whether a new certificate is requested
|
||||||
or the existing certificate is renewed or reissued is based on I(request_type).
|
or the existing certificate is renewed or reissued is based on O(request_type).
|
||||||
type: path
|
type: path
|
||||||
required: true
|
required: true
|
||||||
full_chain_path:
|
full_chain_path:
|
||||||
@@ -67,54 +67,54 @@ options:
|
|||||||
type: path
|
type: path
|
||||||
csr:
|
csr:
|
||||||
description:
|
description:
|
||||||
- Base-64 encoded Certificate Signing Request (CSR). I(csr) is accepted with or without PEM formatting around the Base-64 string.
|
- Base-64 encoded Certificate Signing Request (CSR). O(csr) is accepted with or without PEM formatting around the Base-64 string.
|
||||||
- If no I(csr) is provided when C(request_type=reissue) or C(request_type=renew), the certificate will be generated with the same public key as
|
- If no O(csr) is provided when O(request_type=reissue) or O(request_type=renew), the certificate will be generated with the same public key as
|
||||||
the certificate being renewed or reissued.
|
the certificate being renewed or reissued.
|
||||||
- If I(subject_alt_name) is specified, it will override the subject alternate names in the CSR.
|
- If O(subject_alt_name) is specified, it will override the subject alternate names in the CSR.
|
||||||
- If I(eku) is specified, it will override the extended key usage in the CSR.
|
- If O(eku) is specified, it will override the extended key usage in the CSR.
|
||||||
- If I(ou) is specified, it will override the organizational units "ou=" present in the subject distinguished name of the CSR, if any.
|
- If O(ou) is specified, it will override the organizational units "ou=" present in the subject distinguished name of the CSR, if any.
|
||||||
- The organization "O=" field from the CSR will not be used. It will be replaced in the issued certificate by I(org) if present, and if not present,
|
- The organization "O=" field from the CSR will not be used. It will be replaced in the issued certificate by O(org) if present, and if not present,
|
||||||
the organization tied to I(client_id).
|
the organization tied to O(client_id).
|
||||||
type: str
|
type: str
|
||||||
tracking_id:
|
tracking_id:
|
||||||
description:
|
description:
|
||||||
- The tracking ID of the certificate to reissue or renew.
|
- The tracking ID of the certificate to reissue or renew.
|
||||||
- I(tracking_id) is invalid if C(request_type=new) or C(request_type=validate_only).
|
- O(tracking_id) is invalid if O(request_type=new) or O(request_type=validate_only).
|
||||||
- If there is a certificate present in I(path) and it is an ECS certificate, I(tracking_id) will be ignored.
|
- If there is a certificate present in O(path) and it is an ECS certificate, O(tracking_id) will be ignored.
|
||||||
- If there is no certificate present in I(path) or there is but it is from another provider, the certificate represented by I(tracking_id) will
|
- If there is no certificate present in O(path) or there is but it is from another provider, the certificate represented by O(tracking_id) will
|
||||||
be renewed or reissued and saved to I(path).
|
be renewed or reissued and saved to O(path).
|
||||||
- If there is no certificate present in I(path) and the I(force) and I(remaining_days) parameters do not indicate a new certificate is needed,
|
- If there is no certificate present in O(path) and the O(force) and O(remaining_days) parameters do not indicate a new certificate is needed,
|
||||||
the certificate referenced by I(tracking_id) certificate will be saved to I(path).
|
the certificate referenced by O(tracking_id) certificate will be saved to O(path).
|
||||||
- This can be used when a known certificate is not currently present on a server, but you want to renew or reissue it to be managed by an ansible
|
- This can be used when a known certificate is not currently present on a server, but you want to renew or reissue it to be managed by an ansible
|
||||||
playbook. For example, if you specify C(request_type=renew), I(tracking_id) of an issued certificate, and I(path) to a file that does not exist,
|
playbook. For example, if you specify O(request_type=renew), O(tracking_id) of an issued certificate, and O(path) to a file that does not exist,
|
||||||
the first run of a task will download the certificate specified by I(tracking_id) (assuming it is still valid). Future runs of the task will
|
the first run of a task will download the certificate specified by O(tracking_id) (assuming it is still valid). Future runs of the task will
|
||||||
(if applicable - see I(force) and I(remaining_days)) renew the certificate now present in I(path).
|
(if applicable - see O(force) and O(remaining_days)) renew the certificate now present in O(path).
|
||||||
type: int
|
type: int
|
||||||
remaining_days:
|
remaining_days:
|
||||||
description:
|
description:
|
||||||
- The number of days the certificate must have left being valid. If C(cert_days < remaining_days) then a new certificate will be
|
- The number of days the certificate must have left being valid. If RV(cert_days) < O(remaining_days) then a new certificate will be
|
||||||
obtained using I(request_type).
|
obtained using O(request_type).
|
||||||
- If C(request_type=renew), a renewal will fail if the certificate being renewed has been issued within the past 30 days, so do not set a
|
- If O(request_type=renew), a renewal will fail if the certificate being renewed has been issued within the past 30 days, so do not set a
|
||||||
I(remaining_days) value that is within 30 days of the full lifetime of the certificate being acted upon.
|
O(remaining_days) value that is within 30 days of the full lifetime of the certificate being acted upon.
|
||||||
- For exmaple, if you are requesting Certificates with a 90 day lifetime, do not set I(remaining_days) to a value C(60) or higher).
|
- For example, if you are requesting Certificates with a 90 day lifetime, do not set O(remaining_days) to a value V(60) or higher).
|
||||||
- The I(force) option may be used to ensure that a new certificate is always obtained.
|
- The O(force) option may be used to ensure that a new certificate is always obtained.
|
||||||
type: int
|
type: int
|
||||||
default: 30
|
default: 30
|
||||||
request_type:
|
request_type:
|
||||||
description:
|
description:
|
||||||
- The operation performed if I(tracking_id) references a valid certificate to reissue, or there is already a certificate present in I(path) but
|
- The operation performed if O(tracking_id) references a valid certificate to reissue, or there is already a certificate present in O(path) but
|
||||||
either I(force) is specified or C(cert_days < remaining_days).
|
either O(force) is specified or RV(cert_days) < O(remaining_days).
|
||||||
- Specifying C(request_type=validate_only) means the request will be validated against the ECS API, but no certificate will be issued.
|
- Specifying O(request_type=validate_only) means the request will be validated against the ECS API, but no certificate will be issued.
|
||||||
- Specifying C(request_type=new) means a certificate request will always be submitted and a new certificate issued.
|
- Specifying O(request_type=new) means a certificate request will always be submitted and a new certificate issued.
|
||||||
- Specifying C(request_type=renew) means that an existing certificate (specified by I(tracking_id) if present, otherwise I(path)) will be renewed.
|
- Specifying O(request_type=renew) means that an existing certificate (specified by O(tracking_id) if present, otherwise O(path)) will be renewed.
|
||||||
If there is no certificate to renew, a new certificate is requested.
|
If there is no certificate to renew, a new certificate is requested.
|
||||||
- Specifying C(request_type=reissue) means that an existing certificate (specified by I(tracking_id) if present, otherwise I(path)) will be
|
- Specifying O(request_type=reissue) means that an existing certificate (specified by O(tracking_id) if present, otherwise O(path)) will be
|
||||||
reissued.
|
reissued.
|
||||||
If there is no certificate to reissue, a new certificate is requested.
|
If there is no certificate to reissue, a new certificate is requested.
|
||||||
- If a certificate was issued within the past 30 days, the C(renew) operation is not a valid operation and will fail.
|
- If a certificate was issued within the past 30 days, the V(renew) operation is not a valid operation and will fail.
|
||||||
- Note that C(reissue) is an operation that will result in the revocation of the certificate that is reissued, be cautious with its use.
|
- Note that V(reissue) is an operation that will result in the revocation of the certificate that is reissued, be cautious with its use.
|
||||||
- I(check_mode) is only supported if C(request_type=new)
|
- I(check_mode) is only supported if O(request_type=new)
|
||||||
- For example, setting C(request_type=renew) and C(remaining_days=30) and pointing to the same certificate on multiple playbook runs means that on
|
- For example, setting O(request_type=renew) and O(remaining_days=30) and pointing to the same certificate on multiple playbook runs means that on
|
||||||
the first run new certificate will be requested. It will then be left along on future runs until it is within 30 days of expiry, then the
|
the first run new certificate will be requested. It will then be left along on future runs until it is within 30 days of expiry, then the
|
||||||
ECS "renew" operation will be performed.
|
ECS "renew" operation will be performed.
|
||||||
type: str
|
type: str
|
||||||
@@ -123,57 +123,57 @@ options:
|
|||||||
cert_type:
|
cert_type:
|
||||||
description:
|
description:
|
||||||
- Specify the type of certificate requested.
|
- Specify the type of certificate requested.
|
||||||
- If a certificate is being reissued or renewed, this parameter is ignored, and the C(cert_type) of the initial certificate is used.
|
- If a certificate is being reissued or renewed, this parameter is ignored, and the O(cert_type) of the initial certificate is used.
|
||||||
type: str
|
type: str
|
||||||
choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CODE_SIGNING', 'EV_CODE_SIGNING',
|
choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CODE_SIGNING', 'EV_CODE_SIGNING',
|
||||||
'CDS_INDIVIDUAL', 'CDS_GROUP', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ]
|
'CDS_INDIVIDUAL', 'CDS_GROUP', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ]
|
||||||
subject_alt_name:
|
subject_alt_name:
|
||||||
description:
|
description:
|
||||||
- The subject alternative name identifiers, as an array of values (applies to I(cert_type) with a value of C(STANDARD_SSL), C(ADVANTAGE_SSL),
|
- The subject alternative name identifiers, as an array of values (applies to O(cert_type) with a value of V(STANDARD_SSL), V(ADVANTAGE_SSL),
|
||||||
C(UC_SSL), C(EV_SSL), C(WILDCARD_SSL), C(PRIVATE_SSL), and C(PD_SSL)).
|
V(UC_SSL), V(EV_SSL), V(WILDCARD_SSL), V(PRIVATE_SSL), and V(PD_SSL)).
|
||||||
- If you are requesting a new SSL certificate, and you pass a I(subject_alt_name) parameter, any SAN names in the CSR are ignored.
|
- If you are requesting a new SSL certificate, and you pass a O(subject_alt_name) parameter, any SAN names in the CSR are ignored.
|
||||||
If no subjectAltName parameter is passed, the SAN names in the CSR are used.
|
If no subjectAltName parameter is passed, the SAN names in the CSR are used.
|
||||||
- See I(request_type) to understand more about SANs during reissues and renewals.
|
- See O(request_type) to understand more about SANs during reissues and renewals.
|
||||||
- In the case of certificates of type C(STANDARD_SSL) certificates, if the CN of the certificate is <domain>.<tld> only the www.<domain>.<tld> value
|
- In the case of certificates of type V(STANDARD_SSL) certificates, if the CN of the certificate is <domain>.<tld> only the www.<domain>.<tld> value
|
||||||
is accepted. If the CN of the certificate is www.<domain>.<tld> only the <domain>.<tld> value is accepted.
|
is accepted. If the CN of the certificate is www.<domain>.<tld> only the <domain>.<tld> value is accepted.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
eku:
|
eku:
|
||||||
description:
|
description:
|
||||||
- If specified, overrides the key usage in the I(csr).
|
- If specified, overrides the key usage in the O(csr).
|
||||||
type: str
|
type: str
|
||||||
choices: [ SERVER_AUTH, CLIENT_AUTH, SERVER_AND_CLIENT_AUTH ]
|
choices: [ SERVER_AUTH, CLIENT_AUTH, SERVER_AND_CLIENT_AUTH ]
|
||||||
ct_log:
|
ct_log:
|
||||||
description:
|
description:
|
||||||
- In compliance with browser requirements, this certificate may be posted to the Certificate Transparency (CT) logs. This is a best practice
|
- In compliance with browser requirements, this certificate may be posted to the Certificate Transparency (CT) logs. This is a best practice
|
||||||
technique that helps domain owners monitor certificates issued to their domains. Note that not all certificates are eligible for CT logging.
|
technique that helps domain owners monitor certificates issued to their domains. Note that not all certificates are eligible for CT logging.
|
||||||
- If I(ct_log) is not specified, the certificate uses the account default.
|
- If O(ct_log) is not specified, the certificate uses the account default.
|
||||||
- If I(ct_log) is specified and the account settings allow it, I(ct_log) overrides the account default.
|
- If O(ct_log) is specified and the account settings allow it, O(ct_log) overrides the account default.
|
||||||
- If I(ct_log) is set to C(false), but the account settings are set to "always log", the certificate generation will fail.
|
- If O(ct_log) is set to V(false), but the account settings are set to "always log", the certificate generation will fail.
|
||||||
type: bool
|
type: bool
|
||||||
client_id:
|
client_id:
|
||||||
description:
|
description:
|
||||||
- The client ID to submit the Certificate Signing Request under.
|
- The client ID to submit the Certificate Signing Request under.
|
||||||
- If no client ID is specified, the certificate will be submitted under the primary client with ID of 1.
|
- If no client ID is specified, the certificate will be submitted under the primary client with ID of 1.
|
||||||
- When using a client other than the primary client, the I(org) parameter cannot be specified.
|
- When using a client other than the primary client, the O(org) parameter cannot be specified.
|
||||||
- The issued certificate will have an organization value in the subject distinguished name represented by the client.
|
- The issued certificate will have an organization value in the subject distinguished name represented by the client.
|
||||||
type: int
|
type: int
|
||||||
default: 1
|
default: 1
|
||||||
org:
|
org:
|
||||||
description:
|
description:
|
||||||
- Organization "O=" to include in the certificate.
|
- Organization "O=" to include in the certificate.
|
||||||
- If I(org) is not specified, the organization from the client represented by I(client_id) is used.
|
- If O(org) is not specified, the organization from the client represented by O(client_id) is used.
|
||||||
- Unless the I(cert_type) is C(PD_SSL), this field may not be specified if the value of I(client_id) is not "1" (the primary client).
|
- Unless the O(cert_type) is V(PD_SSL), this field may not be specified if the value of O(client_id) is not "1" (the primary client).
|
||||||
non-primary clients, certificates may only be issued with the organization of that client.
|
non-primary clients, certificates may only be issued with the organization of that client.
|
||||||
type: str
|
type: str
|
||||||
ou:
|
ou:
|
||||||
description:
|
description:
|
||||||
- Organizational unit "OU=" to include in the certificate.
|
- Organizational unit "OU=" to include in the certificate.
|
||||||
- I(ou) behavior is dependent on whether organizational units are enabled for your account. If organizational unit support is disabled for your
|
- O(ou) behavior is dependent on whether organizational units are enabled for your account. If organizational unit support is disabled for your
|
||||||
account, organizational units from the I(csr) and the I(ou) parameter are ignored.
|
account, organizational units from the O(csr) and the O(ou) parameter are ignored.
|
||||||
- If both I(csr) and I(ou) are specified, the value in I(ou) will override the OU fields present in the subject distinguished name in the I(csr)
|
- If both O(csr) and O(ou) are specified, the value in O(ou) will override the OU fields present in the subject distinguished name in the O(csr)
|
||||||
- If neither I(csr) nor I(ou) are specified for a renew or reissue operation, the OU fields in the initial certificate are reused.
|
- If neither O(csr) nor O(ou) are specified for a renew or reissue operation, the OU fields in the initial certificate are reused.
|
||||||
- An invalid OU from I(csr) is ignored, but any invalid organizational units in I(ou) will result in an error indicating "Unapproved OU". The I(ou)
|
- An invalid OU from O(csr) is ignored, but any invalid organizational units in O(ou) will result in an error indicating "Unapproved OU". The O(ou)
|
||||||
parameter can be used to force failure if an unapproved organizational unit is provided.
|
parameter can be used to force failure if an unapproved organizational unit is provided.
|
||||||
- A maximum of one OU may be specified for current products. Multiple OUs are reserved for future products.
|
- A maximum of one OU may be specified for current products. Multiple OUs are reserved for future products.
|
||||||
type: list
|
type: list
|
||||||
@@ -181,10 +181,10 @@ options:
|
|||||||
end_user_key_storage_agreement:
|
end_user_key_storage_agreement:
|
||||||
description:
|
description:
|
||||||
- The end user of the Code Signing certificate must generate and store the private key for this request on cryptographically secure
|
- The end user of the Code Signing certificate must generate and store the private key for this request on cryptographically secure
|
||||||
hardware to be compliant with the Entrust CSP and Subscription agreement. If requesting a certificate of type C(CODE_SIGNING) or
|
hardware to be compliant with the Entrust CSP and Subscription agreement. If requesting a certificate of type V(CODE_SIGNING) or
|
||||||
C(EV_CODE_SIGNING), you must set I(end_user_key_storage_agreement) to true if and only if you acknowledge that you will inform the user of this
|
V(EV_CODE_SIGNING), you must set O(end_user_key_storage_agreement) to true if and only if you acknowledge that you will inform the user of this
|
||||||
requirement.
|
requirement.
|
||||||
- Applicable only to I(cert_type) of values C(CODE_SIGNING) and C(EV_CODE_SIGNING).
|
- Applicable only to O(cert_type) of values V(CODE_SIGNING) and V(EV_CODE_SIGNING).
|
||||||
type: bool
|
type: bool
|
||||||
tracking_info:
|
tracking_info:
|
||||||
description: Free form tracking information to attach to the record for the certificate.
|
description: Free form tracking information to attach to the record for the certificate.
|
||||||
@@ -320,29 +320,29 @@ options:
|
|||||||
cert_expiry:
|
cert_expiry:
|
||||||
description:
|
description:
|
||||||
- The date the certificate should be set to expire, in RFC3339 compliant date or date-time format. For example,
|
- The date the certificate should be set to expire, in RFC3339 compliant date or date-time format. For example,
|
||||||
C(2020-02-23), C(2020-02-23T15:00:00.05Z).
|
V(2020-02-23), V(2020-02-23T15:00:00.05Z).
|
||||||
- I(cert_expiry) is only supported for requests of C(request_type=new) or C(request_type=renew). If C(request_type=reissue),
|
- O(cert_expiry) is only supported for requests of O(request_type=new) or O(request_type=renew). If O(request_type=reissue),
|
||||||
I(cert_expiry) will be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial
|
O(cert_expiry) will be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial
|
||||||
certificate.
|
certificate.
|
||||||
- A reissued certificate will always have the same expiry as the original certificate.
|
- A reissued certificate will always have the same expiry as the original certificate.
|
||||||
- Note that only the date (day, month, year) is supported for specifying the expiry date. If you choose to specify an expiry time with the expiry
|
- Note that only the date (day, month, year) is supported for specifying the expiry date. If you choose to specify an expiry time with the expiry
|
||||||
date, the time will be adjusted to Eastern Standard Time (EST). This could have the unintended effect of moving your expiry date to the previous
|
date, the time will be adjusted to Eastern Standard Time (EST). This could have the unintended effect of moving your expiry date to the previous
|
||||||
day.
|
day.
|
||||||
- Applies only to accounts with a pooling inventory model.
|
- Applies only to accounts with a pooling inventory model.
|
||||||
- Only one of I(cert_expiry) or I(cert_lifetime) may be specified.
|
- Only one of O(cert_expiry) or O(cert_lifetime) may be specified.
|
||||||
type: str
|
type: str
|
||||||
cert_lifetime:
|
cert_lifetime:
|
||||||
description:
|
description:
|
||||||
- The lifetime of the certificate.
|
- The lifetime of the certificate.
|
||||||
- Applies to all certificates for accounts with a non-pooling inventory model.
|
- Applies to all certificates for accounts with a non-pooling inventory model.
|
||||||
- I(cert_lifetime) is only supported for requests of C(request_type=new) or C(request_type=renew). If C(request_type=reissue), I(cert_lifetime) will
|
- O(cert_lifetime) is only supported for requests of O(request_type=new) or O(request_type=renew). If O(request_type=reissue), O(cert_lifetime) will
|
||||||
be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial certificate.
|
be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial certificate.
|
||||||
- Applies to certificates of I(cert_type)=C(CDS_INDIVIDUAL, CDS_GROUP, CDS_ENT_LITE, CDS_ENT_PRO, SMIME_ENT) for accounts with a pooling inventory
|
- Applies to certificates of O(cert_type=CDS_INDIVIDUAL), V(CDS_GROUP), V(CDS_ENT_LITE), V(CDS_ENT_PRO), or V(SMIME_ENT)
|
||||||
model.
|
for accounts with a pooling inventory model.
|
||||||
- C(P1Y) is a certificate with a 1 year lifetime.
|
- V(P1Y) is a certificate with a 1 year lifetime.
|
||||||
- C(P2Y) is a certificate with a 2 year lifetime.
|
- V(P2Y) is a certificate with a 2 year lifetime.
|
||||||
- C(P3Y) is a certificate with a 3 year lifetime.
|
- V(P3Y) is a certificate with a 3 year lifetime.
|
||||||
- Only one of I(cert_expiry) or I(cert_lifetime) may be specified.
|
- Only one of O(cert_expiry) or O(cert_lifetime) may be specified.
|
||||||
type: str
|
type: str
|
||||||
choices: [ P1Y, P2Y, P3Y ]
|
choices: [ P1Y, P2Y, P3Y ]
|
||||||
seealso:
|
seealso:
|
||||||
@@ -350,6 +350,8 @@ seealso:
|
|||||||
description: Can be used to create private keys (both for certificates and accounts).
|
description: Can be used to create private keys (both for certificates and accounts).
|
||||||
- module: community.crypto.openssl_csr
|
- module: community.crypto.openssl_csr
|
||||||
description: Can be used to create a Certificate Signing Request (CSR).
|
description: Can be used to create a Certificate Signing Request (CSR).
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
@@ -476,12 +478,12 @@ filename:
|
|||||||
sample: /etc/ssl/crt/www.ansible.com.crt
|
sample: /etc/ssl/crt/www.ansible.com.crt
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created for the certificate.
|
description: Name of backup file created for the certificate.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~
|
sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~
|
||||||
backup_full_chain_file:
|
backup_full_chain_file:
|
||||||
description: Name of the backup file created for the certificate chain.
|
description: Name of the backup file created for the certificate chain.
|
||||||
returned: changed and if I(backup) is C(true) and I(full_chain_path) is set.
|
returned: changed and if O(backup) is V(true) and O(full_chain_path) is set.
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/ca.chain.crt.2019-03-09@11:22~
|
sample: /path/to/ca.chain.crt.2019-03-09@11:22~
|
||||||
tracking_id:
|
tracking_id:
|
||||||
@@ -490,7 +492,10 @@ tracking_id:
|
|||||||
type: int
|
type: int
|
||||||
sample: 380079
|
sample: 380079
|
||||||
serial_number:
|
serial_number:
|
||||||
description: The serial number of the issued certificate.
|
description:
|
||||||
|
- The serial number of the issued certificate.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 1235262234164342
|
sample: 1235262234164342
|
||||||
@@ -502,8 +507,8 @@ cert_days:
|
|||||||
cert_status:
|
cert_status:
|
||||||
description:
|
description:
|
||||||
- The certificate status in ECS.
|
- The certificate status in ECS.
|
||||||
- 'Current possible values (which may be expanded in the future) are: C(ACTIVE), C(APPROVED), C(DEACTIVATED), C(DECLINED), C(EXPIRED), C(NA),
|
- 'Current possible values (which may be expanded in the future) are: V(ACTIVE), V(APPROVED), V(DEACTIVATED), V(DECLINED), V(EXPIRED), V(NA),
|
||||||
C(PENDING), C(PENDING_QUORUM), C(READY), C(REISSUED), C(REISSUING), C(RENEWED), C(RENEWING), C(REVOKED), C(SUSPENDED)'
|
V(PENDING), V(PENDING_QUORUM), V(READY), V(REISSUED), V(REISSUING), V(RENEWED), V(RENEWING), V(REVOKED), V(SUSPENDED)'
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: ACTIVE
|
sample: ACTIVE
|
||||||
@@ -933,8 +938,8 @@ def main():
|
|||||||
module.fail_json(msg='The cert_expiry field is invalid when request_type="reissue".')
|
module.fail_json(msg='The cert_expiry field is invalid when request_type="reissue".')
|
||||||
elif module.params['cert_lifetime']:
|
elif module.params['cert_lifetime']:
|
||||||
module.fail_json(msg='The cert_lifetime field is invalid when request_type="reissue".')
|
module.fail_json(msg='The cert_lifetime field is invalid when request_type="reissue".')
|
||||||
# Only a reissued request can omit the CSR
|
# Reissued or renew request can omit the CSR
|
||||||
else:
|
elif module.params['request_type'] != 'renew':
|
||||||
module_params_csr = module.params['csr']
|
module_params_csr = module.params['csr']
|
||||||
if module_params_csr is None:
|
if module_params_csr is None:
|
||||||
module.fail_json(msg='The csr field is required when request_type={0}'.format(module.params['request_type']))
|
module.fail_json(msg='The csr field is required when request_type={0}'.format(module.params['request_type']))
|
||||||
|
|||||||
@@ -20,19 +20,19 @@ description:
|
|||||||
- Request validation or re-validation of a domain with the Entrust Certificate Services (ECS) API.
|
- Request validation or re-validation of a domain with the Entrust Certificate Services (ECS) API.
|
||||||
- Requires credentials for the L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API.
|
- Requires credentials for the L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API.
|
||||||
- If the domain is already in the validation process, no new validation will be requested, but the validation data (if applicable) will be returned.
|
- If the domain is already in the validation process, no new validation will be requested, but the validation data (if applicable) will be returned.
|
||||||
- If the domain is already in the validation process but the I(verification_method) specified is different than the current I(verification_method),
|
- If the domain is already in the validation process but the O(verification_method) specified is different than the current O(verification_method),
|
||||||
the I(verification_method) will be updated and validation data (if applicable) will be returned.
|
the O(verification_method) will be updated and validation data (if applicable) will be returned.
|
||||||
- If the domain is an active, validated domain, the return value of I(changed) will be false, unless C(domain_status=EXPIRED), in which case a re-validation
|
- If the domain is an active, validated domain, the return value of C(changed) will be false, unless RV(domain_status=EXPIRED), in which case a
|
||||||
will be performed.
|
re-validation will be performed.
|
||||||
- If C(verification_method=dns), details about the required DNS entry will be specified in the return parameters I(dns_contents), I(dns_location), and
|
- If O(verification_method=dns), details about the required DNS entry will be specified in the return parameters RV(dns_contents), RV(dns_location), and
|
||||||
I(dns_resource_type).
|
RV(dns_resource_type).
|
||||||
- If C(verification_method=web_server), details about the required file details will be specified in the return parameters I(file_contents) and
|
- If O(verification_method=web_server), details about the required file details will be specified in the return parameters RV(file_contents) and
|
||||||
I(file_location).
|
RV(file_location).
|
||||||
- If C(verification_method=email), the email address(es) that the validation email(s) were sent to will be in the return parameter I(emails). This is
|
- If O(verification_method=email), the email address(es) that the validation email(s) were sent to will be in the return parameter RV(emails). This is
|
||||||
purely informational. For domains requested using this module, this will always be a list of size 1.
|
purely informational. For domains requested using this module, this will always be a list of size 1.
|
||||||
notes:
|
notes:
|
||||||
- There is a small delay (typically about 5 seconds, but can be as long as 60 seconds) before obtaining the random values when requesting a validation
|
- There is a small delay (typically about 5 seconds, but can be as long as 60 seconds) before obtaining the random values when requesting a validation
|
||||||
while C(verification_method=dns) or C(verification_method=web_server). Be aware of that if doing many domain validation requests.
|
while O(verification_method=dns) or O(verification_method=web_server). Be aware of that if doing many domain validation requests.
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
- community.crypto.ecs_credential
|
- community.crypto.ecs_credential
|
||||||
@@ -56,35 +56,35 @@ options:
|
|||||||
verification_method:
|
verification_method:
|
||||||
description:
|
description:
|
||||||
- The verification method to be used to prove control of the domain.
|
- The verification method to be used to prove control of the domain.
|
||||||
- If C(verification_method=email) and the value I(verification_email) is specified, that value is used for the email validation. If
|
- If O(verification_method=email) and the value O(verification_email) is specified, that value is used for the email validation. If
|
||||||
I(verification_email) is not provided, the first value present in WHOIS data will be used. An email will be sent to the address in
|
O(verification_email) is not provided, the first value present in WHOIS data will be used. An email will be sent to the address in
|
||||||
I(verification_email) with instructions on how to verify control of the domain.
|
O(verification_email) with instructions on how to verify control of the domain.
|
||||||
- If C(verification_method=dns), the value I(dns_contents) must be stored in location I(dns_location), with a DNS record type of
|
- If O(verification_method=dns), the value RV(dns_contents) must be stored in location RV(dns_location), with a DNS record type of
|
||||||
I(verification_dns_record_type). To prove domain ownership, update your DNS records so the text string returned by I(dns_contents) is available at
|
RV(dns_resource_type). To prove domain ownership, update your DNS records so the text string returned by RV(dns_contents) is available at
|
||||||
I(dns_location).
|
RV(dns_location).
|
||||||
- If C(verification_method=web_server), the contents of return value I(file_contents) must be made available on a web server accessible at location
|
- If O(verification_method=web_server), the contents of return value RV(file_contents) must be made available on a web server accessible at location
|
||||||
I(file_location).
|
RV(file_location).
|
||||||
- If C(verification_method=manual), the domain will be validated with a manual process. This is not recommended.
|
- If O(verification_method=manual), the domain will be validated with a manual process. This is not recommended.
|
||||||
type: str
|
type: str
|
||||||
choices: [ 'dns', 'email', 'manual', 'web_server']
|
choices: [ 'dns', 'email', 'manual', 'web_server']
|
||||||
required: true
|
required: true
|
||||||
verification_email:
|
verification_email:
|
||||||
description:
|
description:
|
||||||
- Email address to be used to verify domain ownership.
|
- Email address to be used to verify domain ownership.
|
||||||
- 'Email address must be either an email address present in the WHOIS data for I(domain_name), or one of the following constructed emails:
|
- 'Email address must be either an email address present in the WHOIS data for O(domain_name), or one of the following constructed emails:
|
||||||
admin@I(domain_name), administrator@I(domain_name), webmaster@I(domain_name), hostmaster@I(domain_name), postmaster@I(domain_name).'
|
admin@O(domain_name), administrator@O(domain_name), webmaster@O(domain_name), hostmaster@O(domain_name), postmaster@O(domain_name).'
|
||||||
- 'Note that if I(domain_name) includes subdomains, the top level domain should be used. For example, if requesting validation of
|
- 'Note that if O(domain_name) includes subdomains, the top level domain should be used. For example, if requesting validation of
|
||||||
example1.ansible.com, or test.example2.ansible.com, and you want to use the "admin" preconstructed name, the email address should be
|
example1.ansible.com, or test.example2.ansible.com, and you want to use the "admin" preconstructed name, the email address should be
|
||||||
admin@ansible.com.'
|
admin@ansible.com.'
|
||||||
- If using the email values from the WHOIS data for the domain or its top level namespace, they must be exact matches.
|
- If using the email values from the WHOIS data for the domain or its top level namespace, they must be exact matches.
|
||||||
- If C(verification_method=email) but I(verification_email) is not provided, the first email address found in WHOIS data for the domain will be
|
- If O(verification_method=email) but O(verification_email) is not provided, the first email address found in WHOIS data for the domain will be
|
||||||
used.
|
used.
|
||||||
- To verify domain ownership, domain owner must follow the instructions in the email they receive.
|
- To verify domain ownership, domain owner must follow the instructions in the email they receive.
|
||||||
- Only allowed if C(verification_method=email)
|
- Only allowed if O(verification_method=email)
|
||||||
type: str
|
type: str
|
||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.x509_certificate
|
- module: community.crypto.x509_certificate
|
||||||
description: Can be used to request certificates from ECS, with C(provider=entrust).
|
description: Can be used to request certificates from ECS, with O(community.crypto.x509_certificate#module:provider=entrust).
|
||||||
- module: community.crypto.ecs_certificate
|
- module: community.crypto.ecs_certificate
|
||||||
description: Can be used to request a Certificate from ECS using a verified domain.
|
description: Can be used to request a Certificate from ECS using a verified domain.
|
||||||
'''
|
'''
|
||||||
@@ -133,72 +133,72 @@ EXAMPLES = r'''
|
|||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
domain_status:
|
domain_status:
|
||||||
description: Status of the current domain. Will be one of C(APPROVED), C(DECLINED), C(CANCELLED), C(INITIAL_VERIFICATION), C(DECLINED), C(CANCELLED),
|
description: Status of the current domain. Will be one of V(APPROVED), V(DECLINED), V(CANCELLED), V(INITIAL_VERIFICATION), V(DECLINED), V(CANCELLED),
|
||||||
C(RE_VERIFICATION), C(EXPIRED), C(EXPIRING)
|
V(RE_VERIFICATION), V(EXPIRED), V(EXPIRING)
|
||||||
returned: changed or success
|
returned: changed or success
|
||||||
type: str
|
type: str
|
||||||
sample: APPROVED
|
sample: APPROVED
|
||||||
verification_method:
|
verification_method:
|
||||||
description: Verification method used to request the domain validation. If C(changed) will be the same as I(verification_method) input parameter.
|
description: Verification method used to request the domain validation. If C(changed) will be the same as O(verification_method) input parameter.
|
||||||
returned: changed or success
|
returned: changed or success
|
||||||
type: str
|
type: str
|
||||||
sample: dns
|
sample: dns
|
||||||
file_location:
|
file_location:
|
||||||
description: The location that ECS will be expecting to be able to find the file for domain verification, containing the contents of I(file_contents).
|
description: The location that ECS will be expecting to be able to find the file for domain verification, containing the contents of RV(file_contents).
|
||||||
returned: I(verification_method) is C(web_server)
|
returned: O(verification_method) is V(web_server)
|
||||||
type: str
|
type: str
|
||||||
sample: http://ansible.com/.well-known/pki-validation/abcd.txt
|
sample: http://ansible.com/.well-known/pki-validation/abcd.txt
|
||||||
file_contents:
|
file_contents:
|
||||||
description: The contents of the file that ECS will be expecting to find at C(file_location).
|
description: The contents of the file that ECS will be expecting to find at RV(file_location).
|
||||||
returned: I(verification_method) is C(web_server)
|
returned: O(verification_method) is V(web_server)
|
||||||
type: str
|
type: str
|
||||||
sample: AB23CD41432522FF2526920393982FAB
|
sample: AB23CD41432522FF2526920393982FAB
|
||||||
emails:
|
emails:
|
||||||
description:
|
description:
|
||||||
- The list of emails used to request validation of this domain.
|
- The list of emails used to request validation of this domain.
|
||||||
- Domains requested using this module will only have a list of size 1.
|
- Domains requested using this module will only have a list of size 1.
|
||||||
returned: I(verification_method) is C(email)
|
returned: O(verification_method) is V(email)
|
||||||
type: list
|
type: list
|
||||||
sample: [ admin@ansible.com, administrator@ansible.com ]
|
sample: [ admin@ansible.com, administrator@ansible.com ]
|
||||||
dns_location:
|
dns_location:
|
||||||
description: The location that ECS will be expecting to be able to find the DNS entry for domain verification, containing the contents of I(dns_contents).
|
description: The location that ECS will be expecting to be able to find the DNS entry for domain verification, containing the contents of RV(dns_contents).
|
||||||
returned: changed and if I(verification_method) is C(dns)
|
returned: changed and if O(verification_method) is V(dns)
|
||||||
type: str
|
type: str
|
||||||
sample: _pki-validation.ansible.com
|
sample: _pki-validation.ansible.com
|
||||||
dns_contents:
|
dns_contents:
|
||||||
description: The value that ECS will be expecting to find in the DNS record located at I(dns_location).
|
description: The value that ECS will be expecting to find in the DNS record located at RV(dns_location).
|
||||||
returned: changed and if I(verification_method) is C(dns)
|
returned: changed and if O(verification_method) is V(dns)
|
||||||
type: str
|
type: str
|
||||||
sample: AB23CD41432522FF2526920393982FAB
|
sample: AB23CD41432522FF2526920393982FAB
|
||||||
dns_resource_type:
|
dns_resource_type:
|
||||||
description: The type of resource record that ECS will be expecting for the DNS record located at I(dns_location).
|
description: The type of resource record that ECS will be expecting for the DNS record located at RV(dns_location).
|
||||||
returned: changed and if I(verification_method) is C(dns)
|
returned: changed and if O(verification_method) is V(dns)
|
||||||
type: str
|
type: str
|
||||||
sample: TXT
|
sample: TXT
|
||||||
client_id:
|
client_id:
|
||||||
description: Client ID that the domain belongs to. If the input value I(client_id) is specified, this will always be the same as I(client_id)
|
description: Client ID that the domain belongs to. If the input value O(client_id) is specified, this will always be the same as O(client_id)
|
||||||
returned: changed or success
|
returned: changed or success
|
||||||
type: int
|
type: int
|
||||||
sample: 1
|
sample: 1
|
||||||
ov_eligible:
|
ov_eligible:
|
||||||
description: Whether the domain is eligible for submission of "OV" certificates. Will never be C(false) if I(ov_eligible) is C(true)
|
description: Whether the domain is eligible for submission of "OV" certificates. Will never be V(false) if RV(ev_eligible) is V(true)
|
||||||
returned: success and I(domain_status) is C(APPROVED), C(RE_VERIFICATION), C(EXPIRING), or C(EXPIRED).
|
returned: success and RV(domain_status) is V(APPROVED), V(RE_VERIFICATION), V(EXPIRING), or V(EXPIRED).
|
||||||
type: bool
|
type: bool
|
||||||
sample: true
|
sample: true
|
||||||
ov_days_remaining:
|
ov_days_remaining:
|
||||||
description: The number of days the domain remains eligible for submission of "OV" certificates. Will never be less than the value of I(ev_days_remaining)
|
description: The number of days the domain remains eligible for submission of "OV" certificates. Will never be less than the value of RV(ev_days_remaining)
|
||||||
returned: success and I(ov_eligible) is C(true) and I(domain_status) is C(APPROVED), C(RE_VERIFICATION) or C(EXPIRING).
|
returned: success and RV(ov_eligible) is V(true) and RV(domain_status) is V(APPROVED), V(RE_VERIFICATION) or V(EXPIRING).
|
||||||
type: int
|
type: int
|
||||||
sample: 129
|
sample: 129
|
||||||
ev_eligible:
|
ev_eligible:
|
||||||
description: Whether the domain is eligible for submission of "EV" certificates. Will never be C(true) if I(ov_eligible) is C(false)
|
description: Whether the domain is eligible for submission of "EV" certificates. Will never be V(true) if RV(ov_eligible) is V(false)
|
||||||
returned: success and I(domain_status) is C(APPROVED), C(RE_VERIFICATION) or C(EXPIRING), or C(EXPIRED).
|
returned: success and RV(domain_status) is V(APPROVED), V(RE_VERIFICATION) or V(EXPIRING), or V(EXPIRED).
|
||||||
type: bool
|
type: bool
|
||||||
sample: true
|
sample: true
|
||||||
ev_days_remaining:
|
ev_days_remaining:
|
||||||
description: The number of days the domain remains eligible for submission of "EV" certificates. Will never be greater than the value of
|
description: The number of days the domain remains eligible for submission of "EV" certificates. Will never be greater than the value of
|
||||||
I(ov_days_remaining)
|
RV(ov_days_remaining)
|
||||||
returned: success and I(ev_eligible) is C(true) and I(domain_status) is C(APPROVED), C(RE_VERIFICATION) or C(EXPIRING).
|
returned: success and RV(ev_eligible) is V(true) and RV(domain_status) is V(APPROVED), V(RE_VERIFICATION) or V(EXPIRING).
|
||||||
type: int
|
type: int
|
||||||
sample: 94
|
sample: 94
|
||||||
|
|
||||||
|
|||||||
@@ -15,166 +15,211 @@ module: get_certificate
|
|||||||
author: "John Westcott IV (@john-westcott-iv)"
|
author: "John Westcott IV (@john-westcott-iv)"
|
||||||
short_description: Get a certificate from a host:port
|
short_description: Get a certificate from a host:port
|
||||||
description:
|
description:
|
||||||
- Makes a secure connection and returns information about the presented certificate
|
- Makes a secure connection and returns information about the presented certificate.
|
||||||
- The module uses the cryptography Python library.
|
- The module uses the cryptography Python library.
|
||||||
- Support SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) only with python >= 2.7.
|
- Support SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) only with Python 2.7 and newer.
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
attributes:
|
attributes:
|
||||||
check_mode:
|
check_mode:
|
||||||
support: none
|
support: none
|
||||||
details:
|
details:
|
||||||
- This action does not modify state.
|
- This action does not modify state.
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: N/A
|
support: N/A
|
||||||
details:
|
details:
|
||||||
- This action does not modify state.
|
- This action does not modify state.
|
||||||
options:
|
options:
|
||||||
host:
|
host:
|
||||||
description:
|
description:
|
||||||
- The host to get the cert for (IP is fine)
|
- The host to get the cert for (IP is fine).
|
||||||
type: str
|
type: str
|
||||||
required: true
|
required: true
|
||||||
ca_cert:
|
ca_cert:
|
||||||
description:
|
description:
|
||||||
- A PEM file containing one or more root certificates; if present, the cert will be validated against these root certs.
|
- A PEM file containing one or more root certificates; if present, the cert will be validated against these root certs.
|
||||||
- Note that this only validates the certificate is signed by the chain; not that the cert is valid for the host presenting it.
|
- Note that this only validates the certificate is signed by the chain; not that the cert is valid for the host presenting it.
|
||||||
type: path
|
type: path
|
||||||
port:
|
port:
|
||||||
description:
|
description:
|
||||||
- The port to connect to
|
- The port to connect to.
|
||||||
type: int
|
type: int
|
||||||
required: true
|
required: true
|
||||||
server_name:
|
server_name:
|
||||||
description:
|
description:
|
||||||
- Server name used for SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) when hostname
|
- Server name used for SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) when hostname
|
||||||
is an IP or is different from server name.
|
is an IP or is different from server name.
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.4.0
|
version_added: 1.4.0
|
||||||
proxy_host:
|
proxy_host:
|
||||||
description:
|
description:
|
||||||
- Proxy host used when get a certificate.
|
- Proxy host used when get a certificate.
|
||||||
type: str
|
type: str
|
||||||
proxy_port:
|
proxy_port:
|
||||||
description:
|
description:
|
||||||
- Proxy port used when get a certificate.
|
- Proxy port used when get a certificate.
|
||||||
type: int
|
type: int
|
||||||
default: 8080
|
default: 8080
|
||||||
starttls:
|
starttls:
|
||||||
description:
|
description:
|
||||||
- Requests a secure connection for protocols which require clients to initiate encryption.
|
- Requests a secure connection for protocols which require clients to initiate encryption.
|
||||||
- Only available for C(mysql) currently.
|
- Only available for V(mysql) currently.
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
- mysql
|
- mysql
|
||||||
version_added: 1.9.0
|
version_added: 1.9.0
|
||||||
timeout:
|
timeout:
|
||||||
description:
|
description:
|
||||||
- The timeout in seconds
|
- The timeout in seconds.
|
||||||
type: int
|
type: int
|
||||||
default: 10
|
default: 10
|
||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
ciphers:
|
ciphers:
|
||||||
description:
|
description:
|
||||||
- SSL/TLS Ciphers to use for the request.
|
- SSL/TLS Ciphers to use for the request.
|
||||||
- 'When a list is provided, all ciphers are joined in order with C(:).'
|
- 'When a list is provided, all ciphers are joined in order with V(:).'
|
||||||
- See the L(OpenSSL Cipher List Format,https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT)
|
- See the L(OpenSSL Cipher List Format,https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT)
|
||||||
for more details.
|
for more details.
|
||||||
- The available ciphers is dependent on the Python and OpenSSL/LibreSSL versions.
|
- The available ciphers is dependent on the Python and OpenSSL/LibreSSL versions.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
version_added: 2.11.0
|
version_added: 2.11.0
|
||||||
asn1_base64:
|
asn1_base64:
|
||||||
description:
|
description:
|
||||||
- Whether to encode the ASN.1 values in the C(extensions) return value with Base64 or not.
|
- Whether to encode the ASN.1 values in the RV(extensions) return value with Base64 or not.
|
||||||
- The documentation claimed for a long time that the values are Base64 encoded, but they
|
- The documentation claimed for a long time that the values are Base64 encoded, but they
|
||||||
never were. For compatibility this option is set to C(false), but that value will eventually
|
never were. For compatibility this option is set to V(false).
|
||||||
be deprecated and changed to C(true).
|
- The default value V(false) is B(deprecated) and will change to V(true) in community.crypto 3.0.0.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
version_added: 2.12.0
|
||||||
version_added: 2.12.0
|
tls_ctx_options:
|
||||||
|
description:
|
||||||
|
- TLS context options (TLS/SSL OP flags) to use for the request.
|
||||||
|
- See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details.
|
||||||
|
- The available TLS context options is dependent on the Python and OpenSSL/LibreSSL versions.
|
||||||
|
type: list
|
||||||
|
elements: raw
|
||||||
|
version_added: 2.21.0
|
||||||
|
get_certificate_chain:
|
||||||
|
description:
|
||||||
|
- If set to V(true), will obtain the certificate chain next to the certificate itself.
|
||||||
|
- The chain as returned by the server can be found in RV(unverified_chain), and the chain that passed validation
|
||||||
|
in RV(verified_chain).
|
||||||
|
- B(Note) that this needs B(Python 3.10 or newer). Also note that only Python 3.13 or newer officially supports this.
|
||||||
|
The module uses internal APIs of Python 3.10, 3.11, and 3.12 to achieve the same. It can be that future versions of
|
||||||
|
Python 3.10, 3.11, or 3.12 break this.
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
version_added: 2.21.0
|
||||||
|
|
||||||
notes:
|
notes:
|
||||||
- When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed.
|
- When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed.
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- "python >= 2.7 when using C(proxy_host)"
|
- "Python >= 2.7 when using O(proxy_host), and Python >= 3.10 when O(get_certificate_chain=true)"
|
||||||
- "cryptography >= 1.6"
|
- "cryptography >= 1.6"
|
||||||
|
|
||||||
|
seealso:
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
cert:
|
cert:
|
||||||
description: The certificate retrieved from the port
|
description: The certificate retrieved from the port.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
expired:
|
expired:
|
||||||
description: Boolean indicating if the cert is expired
|
description: Boolean indicating if the cert is expired.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
extensions:
|
extensions:
|
||||||
description: Extensions applied to the cert
|
description: Extensions applied to the cert.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: dict
|
elements: dict
|
||||||
contains:
|
contains:
|
||||||
critical:
|
critical:
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
description: Whether the extension is critical.
|
description: Whether the extension is critical.
|
||||||
asn1_data:
|
asn1_data:
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
- The ASN.1 content of the extension.
|
- The ASN.1 content of the extension.
|
||||||
- If I(asn1_base64=true) this will be Base64 encoded, otherwise the raw
|
- If O(asn1_base64=true) this will be Base64 encoded, otherwise the raw
|
||||||
binary value will be returned.
|
binary value will be returned.
|
||||||
- Please note that the raw binary value might not survive JSON serialization
|
- Please note that the raw binary value might not survive JSON serialization
|
||||||
to the Ansible controller, and also might cause failures when displaying it.
|
to the Ansible controller, and also might cause failures when displaying it.
|
||||||
See U(https://github.com/ansible/ansible/issues/80258) for more information.
|
See U(https://github.com/ansible/ansible/issues/80258) for more information.
|
||||||
- B(Note) that depending on the C(cryptography) version used, it is
|
- 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
|
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
|
to provide the re-encoded content of the extension in case it was
|
||||||
parsed by C(cryptography). This should usually result in exactly the
|
parsed by C(cryptography). This should usually result in exactly the
|
||||||
same value, except if the original extension value was malformed.
|
same value, except if the original extension value was malformed.
|
||||||
name:
|
name:
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
description: The extension's name.
|
description: The extension's name.
|
||||||
issuer:
|
issuer:
|
||||||
description: Information about the issuer of the cert
|
description: Information about the issuer of the cert.
|
||||||
returned: success
|
returned: success
|
||||||
type: dict
|
type: dict
|
||||||
not_after:
|
not_after:
|
||||||
description: Expiration date of the cert
|
description: Expiration date of the cert.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
not_before:
|
not_before:
|
||||||
description: Issue date of the cert
|
description: Issue date of the cert.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
serial_number:
|
serial_number:
|
||||||
description: The serial number of the cert
|
description:
|
||||||
returned: success
|
- The serial number of the cert.
|
||||||
type: str
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
|
returned: success
|
||||||
|
type: int
|
||||||
signature_algorithm:
|
signature_algorithm:
|
||||||
description: The algorithm used to sign the cert
|
description: The algorithm used to sign the cert.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
subject:
|
subject:
|
||||||
description: Information about the subject of the cert (OU, CN, etc)
|
description: Information about the subject of the cert (C(OU), C(CN), and so on).
|
||||||
returned: success
|
returned: success
|
||||||
type: dict
|
type: dict
|
||||||
version:
|
version:
|
||||||
description: The version number of the certificate
|
description: The version number of the certificate.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
|
verified_chain:
|
||||||
|
description:
|
||||||
|
- The verified certificate chain retrieved from the port.
|
||||||
|
- The first entry is always RV(cert).
|
||||||
|
- The last certificate the root certificate the chain is traced to. If O(ca_cert) is provided this certificate is part of that store;
|
||||||
|
otherwise it is part of the store used by default by Python.
|
||||||
|
- Note that RV(unverified_chain) generally does not contain the root certificate, and might contain other certificates that are not part
|
||||||
|
of the validated chain.
|
||||||
|
returned: success and O(get_certificate_chain=true)
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
version_added: 2.21.0
|
||||||
|
unverified_chain:
|
||||||
|
description:
|
||||||
|
- The certificate chain retrieved from the port.
|
||||||
|
- The first entry is always RV(cert).
|
||||||
|
returned: success and O(get_certificate_chain=true)
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
version_added: 2.21.0
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
@@ -195,29 +240,59 @@ EXAMPLES = '''
|
|||||||
register: cert
|
register: cert
|
||||||
|
|
||||||
- name: How many days until cert expires
|
- name: How many days until cert expires
|
||||||
debug:
|
ansible.builtin.debug:
|
||||||
msg: "cert expires in: {{ expire_days }} days."
|
msg: "cert expires in: {{ expire_days }} days."
|
||||||
vars:
|
vars:
|
||||||
expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}"
|
expire_days: >-
|
||||||
|
{{ (
|
||||||
|
(cert.not_after | ansible.builtin.to_datetime('%Y%m%d%H%M%SZ')) -
|
||||||
|
(ansible_date_time.iso8601 | ansible.builtin.to_datetime('%Y-%m-%dT%H:%M:%SZ'))
|
||||||
|
).days }}
|
||||||
|
|
||||||
|
- name: Allow legacy insecure renegotiation to get a cert from a legacy device
|
||||||
|
community.crypto.get_certificate:
|
||||||
|
host: "legacy-device.domain.com"
|
||||||
|
port: 443
|
||||||
|
ciphers:
|
||||||
|
- HIGH
|
||||||
|
tls_ctx_options:
|
||||||
|
- OP_ALL
|
||||||
|
- OP_NO_SSLv3
|
||||||
|
- OP_CIPHER_SERVER_PREFERENCE
|
||||||
|
- OP_ENABLE_MIDDLEBOX_COMPAT
|
||||||
|
- OP_NO_COMPRESSION
|
||||||
|
- 4 # OP_LEGACY_SERVER_CONNECT
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
register: legacy_cert
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
|
||||||
import traceback
|
import traceback
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
from socket import create_connection, setdefaulttimeout, socket
|
from socket import create_connection, setdefaulttimeout, socket
|
||||||
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
|
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
cryptography_oid_to_name,
|
cryptography_oid_to_name,
|
||||||
cryptography_get_extensions_from_cert,
|
cryptography_get_extensions_from_cert,
|
||||||
|
get_not_valid_after,
|
||||||
|
get_not_valid_before,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
get_now_datetime,
|
||||||
)
|
)
|
||||||
|
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||||
@@ -272,7 +347,9 @@ def main():
|
|||||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||||
starttls=dict(type='str', choices=['mysql']),
|
starttls=dict(type='str', choices=['mysql']),
|
||||||
ciphers=dict(type='list', elements='str'),
|
ciphers=dict(type='list', elements='str'),
|
||||||
asn1_base64=dict(type='bool', default=False),
|
asn1_base64=dict(type='bool'),
|
||||||
|
tls_ctx_options=dict(type='list', elements='raw'),
|
||||||
|
get_certificate_chain=dict(type='bool', default=False),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -286,6 +363,24 @@ def main():
|
|||||||
start_tls_server_type = module.params.get('starttls')
|
start_tls_server_type = module.params.get('starttls')
|
||||||
ciphers = module.params.get('ciphers')
|
ciphers = module.params.get('ciphers')
|
||||||
asn1_base64 = module.params['asn1_base64']
|
asn1_base64 = module.params['asn1_base64']
|
||||||
|
tls_ctx_options = module.params['tls_ctx_options']
|
||||||
|
get_certificate_chain = module.params['get_certificate_chain']
|
||||||
|
|
||||||
|
if asn1_base64 is None:
|
||||||
|
module.deprecate(
|
||||||
|
'The default value `false` for asn1_base64 is deprecated and will change to `true` in '
|
||||||
|
'community.crypto 3.0.0. If you need this value, it is best to set the value explicitly '
|
||||||
|
'and adjust your roles/playbooks to use `asn1_base64=true` as soon as possible',
|
||||||
|
version='3.0.0',
|
||||||
|
collection_name='community.crypto',
|
||||||
|
)
|
||||||
|
asn1_base64 = False
|
||||||
|
|
||||||
|
if get_certificate_chain and sys.version_info < (3, 10):
|
||||||
|
module.fail_json(
|
||||||
|
msg='get_certificate_chain=true can only be used with Python 3.10 (Python 3.13+ officially supports this). '
|
||||||
|
'The Python version used to run the get_certificate module is %s' % sys.version
|
||||||
|
)
|
||||||
|
|
||||||
backend = module.params.get('select_crypto_backend')
|
backend = module.params.get('select_crypto_backend')
|
||||||
if backend == 'auto':
|
if backend == 'auto':
|
||||||
@@ -317,6 +412,9 @@ def main():
|
|||||||
if not isfile(ca_cert):
|
if not isfile(ca_cert):
|
||||||
module.fail_json(msg="ca_cert file does not exist")
|
module.fail_json(msg="ca_cert file does not exist")
|
||||||
|
|
||||||
|
verified_chain = None
|
||||||
|
unverified_chain = None
|
||||||
|
|
||||||
if not HAS_CREATE_DEFAULT_CONTEXT:
|
if not HAS_CREATE_DEFAULT_CONTEXT:
|
||||||
# Python < 2.7.9
|
# Python < 2.7.9
|
||||||
if proxy_host:
|
if proxy_host:
|
||||||
@@ -325,6 +423,9 @@ def main():
|
|||||||
if ciphers is not None:
|
if ciphers is not None:
|
||||||
module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.',
|
module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.',
|
||||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
||||||
|
if tls_ctx_options is not None:
|
||||||
|
module.fail_json(msg='To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.',
|
||||||
|
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
||||||
try:
|
try:
|
||||||
# Note: get_server_certificate does not support SNI!
|
# Note: get_server_certificate does not support SNI!
|
||||||
cert = get_server_certificate((host, port), ca_certs=ca_cert)
|
cert = get_server_certificate((host, port), ca_certs=ca_cert)
|
||||||
@@ -360,8 +461,76 @@ def main():
|
|||||||
ciphers_joined = ":".join(ciphers)
|
ciphers_joined = ":".join(ciphers)
|
||||||
ctx.set_ciphers(ciphers_joined)
|
ctx.set_ciphers(ciphers_joined)
|
||||||
|
|
||||||
cert = ctx.wrap_socket(sock, server_hostname=server_name or host).getpeercert(True)
|
if tls_ctx_options is not None:
|
||||||
|
# Clear default ctx options
|
||||||
|
ctx.options = 0
|
||||||
|
|
||||||
|
# For each item in the tls_ctx_options list
|
||||||
|
for tls_ctx_option in tls_ctx_options:
|
||||||
|
# If the item is a string_type
|
||||||
|
if isinstance(tls_ctx_option, string_types):
|
||||||
|
# Convert tls_ctx_option to a native string
|
||||||
|
tls_ctx_option_str = to_native(tls_ctx_option)
|
||||||
|
# Get the tls_ctx_option_str attribute from ssl
|
||||||
|
tls_ctx_option_attr = getattr(ssl, tls_ctx_option_str, None)
|
||||||
|
# If tls_ctx_option_attr is an integer
|
||||||
|
if isinstance(tls_ctx_option_attr, int):
|
||||||
|
# Set tls_ctx_option_int to the attribute value
|
||||||
|
tls_ctx_option_int = tls_ctx_option_attr
|
||||||
|
# If tls_ctx_option_attr is not an integer
|
||||||
|
else:
|
||||||
|
module.fail_json(msg="Failed to determine the numeric value for {0}".format(tls_ctx_option_str))
|
||||||
|
# If the item is an integer
|
||||||
|
elif isinstance(tls_ctx_option, int):
|
||||||
|
# Set tls_ctx_option_int to the item value
|
||||||
|
tls_ctx_option_int = tls_ctx_option
|
||||||
|
# If the item is not a string nor integer
|
||||||
|
else:
|
||||||
|
module.fail_json(msg="tls_ctx_options must be a string or integer, got {0!r}".format(tls_ctx_option))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Add the int value of the item to ctx options
|
||||||
|
ctx.options |= tls_ctx_option_int
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg="Failed to add {0} to CTX options".format(tls_ctx_option_str or tls_ctx_option_int))
|
||||||
|
|
||||||
|
tls_sock = ctx.wrap_socket(sock, server_hostname=server_name or host)
|
||||||
|
cert = tls_sock.getpeercert(True)
|
||||||
cert = DER_cert_to_PEM_cert(cert)
|
cert = DER_cert_to_PEM_cert(cert)
|
||||||
|
|
||||||
|
if get_certificate_chain:
|
||||||
|
if sys.version_info < (3, 13):
|
||||||
|
# The official way to access this has been added in https://github.com/python/cpython/pull/109113/files.
|
||||||
|
# We're basically doing the same for older Python versions. The internal API needed for this was added
|
||||||
|
# in https://github.com/python/cpython/commit/666991fc598bc312d72aff0078ecb553f0a968f1, which was first
|
||||||
|
# released in Python 3.10.0.
|
||||||
|
def _convert_chain(chain):
|
||||||
|
if not chain:
|
||||||
|
return []
|
||||||
|
return [c.public_bytes(ssl._ssl.ENCODING_DER) for c in chain]
|
||||||
|
|
||||||
|
ssl_obj = tls_sock._sslobj # This is of type ssl._ssl._SSLSocket
|
||||||
|
verified_der_chain = _convert_chain(ssl_obj.get_verified_chain())
|
||||||
|
unverified_der_chain = _convert_chain(ssl_obj.get_unverified_chain())
|
||||||
|
else:
|
||||||
|
# This works with Python 3.13+
|
||||||
|
|
||||||
|
# Unfortunately due to a bug (https://github.com/python/cpython/issues/118658) some early pre-releases of
|
||||||
|
# Python 3.13 do not return lists of byte strings, but lists of _ssl.Certificate objects. This is going to
|
||||||
|
# be fixed by https://github.com/python/cpython/pull/118669. For now we convert the certificates ourselves
|
||||||
|
# if they are not byte strings to work around this.
|
||||||
|
def _convert_chain(chain):
|
||||||
|
return [
|
||||||
|
c if isinstance(c, bytes) else c.public_bytes(ssl._ssl.ENCODING_DER)
|
||||||
|
for c in chain
|
||||||
|
]
|
||||||
|
|
||||||
|
verified_der_chain = _convert_chain(tls_sock.get_verified_chain())
|
||||||
|
unverified_der_chain = _convert_chain(tls_sock.get_unverified_chain())
|
||||||
|
|
||||||
|
verified_chain = [DER_cert_to_PEM_cert(c) for c in verified_der_chain]
|
||||||
|
unverified_chain = [DER_cert_to_PEM_cert(c) for c in unverified_der_chain]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if proxy_host:
|
if proxy_host:
|
||||||
module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format(
|
module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format(
|
||||||
@@ -377,7 +546,7 @@ def main():
|
|||||||
for attribute in x509.subject:
|
for attribute in x509.subject:
|
||||||
result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||||
|
|
||||||
result['expired'] = x509.not_valid_after < datetime.datetime.utcnow()
|
result['expired'] = get_not_valid_after(x509) < get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
|
|
||||||
result['extensions'] = []
|
result['extensions'] = []
|
||||||
for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items():
|
for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items():
|
||||||
@@ -395,8 +564,8 @@ def main():
|
|||||||
for attribute in x509.issuer:
|
for attribute in x509.issuer:
|
||||||
result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||||
|
|
||||||
result['not_after'] = x509.not_valid_after.strftime('%Y%m%d%H%M%SZ')
|
result['not_after'] = get_not_valid_after(x509).strftime('%Y%m%d%H%M%SZ')
|
||||||
result['not_before'] = x509.not_valid_before.strftime('%Y%m%d%H%M%SZ')
|
result['not_before'] = get_not_valid_before(x509).strftime('%Y%m%d%H%M%SZ')
|
||||||
|
|
||||||
result['serial_number'] = x509.serial_number
|
result['serial_number'] = x509.serial_number
|
||||||
result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid)
|
result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid)
|
||||||
@@ -409,6 +578,11 @@ def main():
|
|||||||
else:
|
else:
|
||||||
result['version'] = "unknown"
|
result['version'] = "unknown"
|
||||||
|
|
||||||
|
if verified_chain is not None:
|
||||||
|
result['verified_chain'] = verified_chain
|
||||||
|
if unverified_chain is not None:
|
||||||
|
result['unverified_chain'] = unverified_chain
|
||||||
|
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,55 +30,66 @@ attributes:
|
|||||||
options:
|
options:
|
||||||
device:
|
device:
|
||||||
description:
|
description:
|
||||||
- "Device to work with (for example C(/dev/sda1)). Needed in most cases.
|
- "Device to work with (for example V(/dev/sda1)). Needed in most cases.
|
||||||
Can be omitted only when I(state=closed) together with I(name)
|
Can be omitted only when O(state=closed) together with O(name)
|
||||||
is provided."
|
is provided."
|
||||||
type: str
|
type: str
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- "Desired state of the LUKS container. Based on its value creates,
|
- "Desired state of the LUKS container. Based on its value creates,
|
||||||
destroys, opens or closes the LUKS container on a given device."
|
destroys, opens or closes the LUKS container on a given device."
|
||||||
- "I(present) will create LUKS container unless already present.
|
- "V(present) will create LUKS container unless already present.
|
||||||
Requires I(device) and either I(keyfile) or I(passphrase) options
|
Requires O(device) and either O(keyfile) or O(passphrase) options
|
||||||
to be provided."
|
to be provided."
|
||||||
- "I(absent) will remove existing LUKS container if it exists.
|
- "V(absent) will remove existing LUKS container if it exists.
|
||||||
Requires I(device) or I(name) to be specified."
|
Requires O(device) or O(name) to be specified."
|
||||||
- "I(opened) will unlock the LUKS container. If it does not exist
|
- "V(opened) will unlock the LUKS container. If it does not exist
|
||||||
it will be created first.
|
it will be created first.
|
||||||
Requires I(device) and either I(keyfile) or I(passphrase)
|
Requires O(device) and either O(keyfile) or O(passphrase)
|
||||||
to be specified. Use the I(name) option to set the name of
|
to be specified. Use the O(name) option to set the name of
|
||||||
the opened container. Otherwise the name will be
|
the opened container. Otherwise the name will be
|
||||||
generated automatically and returned as a part of the
|
generated automatically and returned as a part of the
|
||||||
result."
|
result."
|
||||||
- "I(closed) will lock the LUKS container. However if the container
|
- "V(closed) will lock the LUKS container. However if the container
|
||||||
does not exist it will be created.
|
does not exist it will be created.
|
||||||
Requires I(device) and either I(keyfile) or I(passphrase)
|
Requires O(device) and either O(keyfile) or O(passphrase)
|
||||||
options to be provided. If container does already exist
|
options to be provided. If container does already exist
|
||||||
I(device) or I(name) will suffice."
|
O(device) or O(name) will suffice."
|
||||||
type: str
|
type: str
|
||||||
default: present
|
default: present
|
||||||
choices: [present, absent, opened, closed]
|
choices: [present, absent, opened, closed]
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- "Sets container name when I(state=opened). Can be used
|
- "Sets container name when O(state=opened). Can be used
|
||||||
instead of I(device) when closing the existing container
|
instead of O(device) when closing the existing container
|
||||||
(that is, when I(state=closed))."
|
(that is, when O(state=closed))."
|
||||||
type: str
|
type: str
|
||||||
keyfile:
|
keyfile:
|
||||||
description:
|
description:
|
||||||
- "Used to unlock the container. Either a I(keyfile) or a
|
- "Used to unlock the container. Either a O(keyfile) or a
|
||||||
I(passphrase) is needed for most of the operations. Parameter
|
O(passphrase) is needed for most of the operations. Parameter
|
||||||
value is the path to the keyfile with the passphrase."
|
value is the path to the keyfile with the passphrase."
|
||||||
- "BEWARE that working with keyfiles in plaintext is dangerous.
|
- "BEWARE that working with keyfiles in plaintext is dangerous.
|
||||||
Make sure that they are protected."
|
Make sure that they are protected."
|
||||||
type: path
|
type: path
|
||||||
passphrase:
|
passphrase:
|
||||||
description:
|
description:
|
||||||
- "Used to unlock the container. Either a I(passphrase) or a
|
- "Used to unlock the container. Either a O(passphrase) or a
|
||||||
I(keyfile) is needed for most of the operations. Parameter
|
O(keyfile) is needed for most of the operations. Parameter
|
||||||
value is a string with the passphrase."
|
value is a string with the passphrase."
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
|
keyslot:
|
||||||
|
description:
|
||||||
|
- "Adds the O(keyfile) or O(passphrase) to a specific keyslot when
|
||||||
|
creating a new container on O(device). Parameter value is the
|
||||||
|
number of the keyslot."
|
||||||
|
- "B(Note) that a device of O(type=luks1) supports the keyslot numbers
|
||||||
|
V(0)-V(7) and a device of O(type=luks2) supports the keyslot numbers
|
||||||
|
V(0)-V(31). In order to use the keyslots V(8)-V(31) when creating a new
|
||||||
|
container, setting O(type) to V(luks2) is required."
|
||||||
|
type: int
|
||||||
|
version_added: '2.16.0'
|
||||||
keysize:
|
keysize:
|
||||||
description:
|
description:
|
||||||
- "Sets the key size only if LUKS container does not exist."
|
- "Sets the key size only if LUKS container does not exist."
|
||||||
@@ -86,8 +97,8 @@ options:
|
|||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
new_keyfile:
|
new_keyfile:
|
||||||
description:
|
description:
|
||||||
- "Adds additional key to given container on I(device).
|
- "Adds additional key to given container on O(device).
|
||||||
Needs I(keyfile) or I(passphrase) option for authorization.
|
Needs O(keyfile) or O(passphrase) option for authorization.
|
||||||
LUKS container supports up to 8 keyslots. Parameter value
|
LUKS container supports up to 8 keyslots. Parameter value
|
||||||
is the path to the keyfile with the passphrase."
|
is the path to the keyfile with the passphrase."
|
||||||
- "NOTE that adding additional keys is idempotent only since
|
- "NOTE that adding additional keys is idempotent only since
|
||||||
@@ -99,8 +110,8 @@ options:
|
|||||||
type: path
|
type: path
|
||||||
new_passphrase:
|
new_passphrase:
|
||||||
description:
|
description:
|
||||||
- "Adds additional passphrase to given container on I(device).
|
- "Adds additional passphrase to given container on O(device).
|
||||||
Needs I(keyfile) or I(passphrase) option for authorization. LUKS
|
Needs O(keyfile) or O(passphrase) option for authorization. LUKS
|
||||||
container supports up to 8 keyslots. Parameter value is a string
|
container supports up to 8 keyslots. Parameter value is a string
|
||||||
with the new passphrase."
|
with the new passphrase."
|
||||||
- "NOTE that adding additional passphrase is idempotent only since
|
- "NOTE that adding additional passphrase is idempotent only since
|
||||||
@@ -108,34 +119,55 @@ options:
|
|||||||
be used even if another keyslot already exists for this passphrase."
|
be used even if another keyslot already exists for this passphrase."
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
|
new_keyslot:
|
||||||
|
description:
|
||||||
|
- "Adds the additional O(new_keyfile) or O(new_passphrase) to a
|
||||||
|
specific keyslot on the given O(device). Parameter value is the number
|
||||||
|
of the keyslot."
|
||||||
|
- "B(Note) that a device of O(type=luks1) supports the keyslot numbers
|
||||||
|
V(0)-V(7) and a device of O(type=luks2) supports the keyslot numbers
|
||||||
|
V(0)-V(31)."
|
||||||
|
type: int
|
||||||
|
version_added: '2.16.0'
|
||||||
remove_keyfile:
|
remove_keyfile:
|
||||||
description:
|
description:
|
||||||
- "Removes given key from the container on I(device). Does not
|
- "Removes given key from the container on O(device). Does not
|
||||||
remove the keyfile from filesystem.
|
remove the keyfile from filesystem.
|
||||||
Parameter value is the path to the keyfile with the passphrase."
|
Parameter value is the path to the keyfile with the passphrase."
|
||||||
- "NOTE that removing keys is idempotent only since
|
- "NOTE that removing keys is idempotent only since
|
||||||
community.crypto 1.4.0. For older versions, trying to remove
|
community.crypto 1.4.0. For older versions, trying to remove
|
||||||
a key which no longer exists results in an error."
|
a key which no longer exists results in an error."
|
||||||
- "NOTE that to remove the last key from a LUKS container, the
|
- "NOTE that to remove the last key from a LUKS container, the
|
||||||
I(force_remove_last_key) option must be set to C(true)."
|
O(force_remove_last_key) option must be set to V(true)."
|
||||||
- "BEWARE that working with keyfiles in plaintext is dangerous.
|
- "BEWARE that working with keyfiles in plaintext is dangerous.
|
||||||
Make sure that they are protected."
|
Make sure that they are protected."
|
||||||
type: path
|
type: path
|
||||||
remove_passphrase:
|
remove_passphrase:
|
||||||
description:
|
description:
|
||||||
- "Removes given passphrase from the container on I(device).
|
- "Removes given passphrase from the container on O(device).
|
||||||
Parameter value is a string with the passphrase to remove."
|
Parameter value is a string with the passphrase to remove."
|
||||||
- "NOTE that removing passphrases is idempotent only since
|
- "NOTE that removing passphrases is idempotent only since
|
||||||
community.crypto 1.4.0. For older versions, trying to remove
|
community.crypto 1.4.0. For older versions, trying to remove
|
||||||
a passphrase which no longer exists results in an error."
|
a passphrase which no longer exists results in an error."
|
||||||
- "NOTE that to remove the last keyslot from a LUKS
|
- "NOTE that to remove the last keyslot from a LUKS
|
||||||
container, the I(force_remove_last_key) option must be set
|
container, the O(force_remove_last_key) option must be set
|
||||||
to C(true)."
|
to V(true)."
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
|
remove_keyslot:
|
||||||
|
description:
|
||||||
|
- "Removes the key in the given slot on O(device). Needs
|
||||||
|
O(keyfile) or O(passphrase) for authorization."
|
||||||
|
- "B(Note) that a device of O(type=luks1) supports the keyslot numbers
|
||||||
|
V(0)-V(7) and a device of O(type=luks2) supports the keyslot numbers
|
||||||
|
V(0)-V(31)."
|
||||||
|
- "B(Note) that the given O(keyfile) or O(passphrase) must not be
|
||||||
|
in the slot to be removed."
|
||||||
|
type: int
|
||||||
|
version_added: '2.16.0'
|
||||||
force_remove_last_key:
|
force_remove_last_key:
|
||||||
description:
|
description:
|
||||||
- "If set to C(true), allows removing the last key from a container."
|
- "If set to V(true), allows removing the last key from a container."
|
||||||
- "BEWARE that when the last key has been removed from a container,
|
- "BEWARE that when the last key has been removed from a container,
|
||||||
the container can no longer be opened!"
|
the container can no longer be opened!"
|
||||||
type: bool
|
type: bool
|
||||||
@@ -145,21 +177,21 @@ options:
|
|||||||
- "This option allow the user to create a LUKS2 format container
|
- "This option allow the user to create a LUKS2 format container
|
||||||
with label support, respectively to identify the container by
|
with label support, respectively to identify the container by
|
||||||
label on later usages."
|
label on later usages."
|
||||||
- "Will only be used on container creation, or when I(device) is
|
- "Will only be used on container creation, or when O(device) is
|
||||||
not specified."
|
not specified."
|
||||||
- "This cannot be specified if I(type) is set to C(luks1)."
|
- "This cannot be specified if O(type) is set to V(luks1)."
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
uuid:
|
uuid:
|
||||||
description:
|
description:
|
||||||
- "With this option user can identify the LUKS container by UUID."
|
- "With this option user can identify the LUKS container by UUID."
|
||||||
- "Will only be used when I(device) and I(label) are not specified."
|
- "Will only be used when O(device) and O(label) are not specified."
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- "This option allow the user explicit define the format of LUKS
|
- "This option allow the user explicit define the format of LUKS
|
||||||
container that wants to work with. Options are C(luks1) or C(luks2)"
|
container that wants to work with. Options are V(luks1) or V(luks2)"
|
||||||
type: str
|
type: str
|
||||||
choices: [luks1, luks2]
|
choices: [luks1, luks2]
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
@@ -168,8 +200,8 @@ options:
|
|||||||
- "This option allows the user to define the cipher specification
|
- "This option allows the user to define the cipher specification
|
||||||
string for the LUKS container."
|
string for the LUKS container."
|
||||||
- "Will only be used on container creation."
|
- "Will only be used on container creation."
|
||||||
- "For pre-2.6.10 kernels, use C(aes-plain) as they do not understand
|
- "For pre-2.6.10 kernels, use V(aes-plain) as they do not understand
|
||||||
the new cipher spec strings. To use ESSIV, use C(aes-cbc-essiv:sha256)."
|
the new cipher spec strings. To use ESSIV, use V(aes-cbc-essiv:sha256)."
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.1.0'
|
version_added: '1.1.0'
|
||||||
hash:
|
hash:
|
||||||
@@ -193,12 +225,12 @@ options:
|
|||||||
- Specify the iteration time used for the PBKDF.
|
- Specify the iteration time used for the PBKDF.
|
||||||
- Note that this is in B(seconds), not in milliseconds as on the
|
- Note that this is in B(seconds), not in milliseconds as on the
|
||||||
command line.
|
command line.
|
||||||
- Mutually exclusive with I(iteration_count).
|
- Mutually exclusive with O(pbkdf.iteration_count).
|
||||||
type: float
|
type: float
|
||||||
iteration_count:
|
iteration_count:
|
||||||
description:
|
description:
|
||||||
- Specify the iteration count used for the PBKDF.
|
- Specify the iteration count used for the PBKDF.
|
||||||
- Mutually exclusive with I(iteration_time).
|
- Mutually exclusive with O(pbkdf.iteration_time).
|
||||||
type: int
|
type: int
|
||||||
algorithm:
|
algorithm:
|
||||||
description:
|
description:
|
||||||
@@ -261,19 +293,26 @@ options:
|
|||||||
persistent:
|
persistent:
|
||||||
description:
|
description:
|
||||||
- "Allows the user to store options into container's metadata persistently and automatically use them next time.
|
- "Allows the user to store options into container's metadata persistently and automatically use them next time.
|
||||||
Only I(perf_same_cpu_crypt), I(perf_submit_from_crypt_cpus), I(perf_no_read_workqueue), and I(perf_no_write_workqueue)
|
Only O(perf_same_cpu_crypt), O(perf_submit_from_crypt_cpus), O(perf_no_read_workqueue), O(perf_no_write_workqueue),
|
||||||
can be stored persistently."
|
and O(allow_discards) can be stored persistently."
|
||||||
- "Will only work with LUKS2 containers."
|
- "Will only work with LUKS2 containers."
|
||||||
- "Will only be used when opening containers."
|
- "Will only be used when opening containers."
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
version_added: '2.3.0'
|
version_added: '2.3.0'
|
||||||
|
allow_discards:
|
||||||
|
description:
|
||||||
|
- "Allow discards (also known as TRIM) requests for device."
|
||||||
|
- "Will only be used when opening containers."
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
version_added: '2.17.0'
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- "cryptsetup"
|
- "cryptsetup"
|
||||||
- "wipefs (when I(state) is C(absent))"
|
- "wipefs (when O(state) is V(absent))"
|
||||||
- "lsblk"
|
- "lsblk"
|
||||||
- "blkid (when I(label) or I(uuid) options are used)"
|
- "blkid (when O(label) or O(uuid) options are used)"
|
||||||
|
|
||||||
author: Jan Pokorny (@japokorn)
|
author: Jan Pokorny (@japokorn)
|
||||||
'''
|
'''
|
||||||
@@ -377,12 +416,32 @@ EXAMPLES = '''
|
|||||||
state: "present"
|
state: "present"
|
||||||
keyfile: "/vault/keyfile"
|
keyfile: "/vault/keyfile"
|
||||||
type: luks2
|
type: luks2
|
||||||
|
|
||||||
|
- name: Create a container with key in slot 4
|
||||||
|
community.crypto.luks_device:
|
||||||
|
device: "/dev/loop0"
|
||||||
|
state: "present"
|
||||||
|
keyfile: "/vault/keyfile"
|
||||||
|
keyslot: 4
|
||||||
|
|
||||||
|
- name: Add a new key in slot 5
|
||||||
|
community.crypto.luks_device:
|
||||||
|
device: "/dev/loop0"
|
||||||
|
keyfile: "/vault/keyfile"
|
||||||
|
new_keyfile: "/vault/keyfile"
|
||||||
|
new_keyslot: 5
|
||||||
|
|
||||||
|
- name: Remove the key from slot 4 (given keyfile must not be slot 4)
|
||||||
|
community.crypto.luks_device:
|
||||||
|
device: "/dev/loop0"
|
||||||
|
keyfile: "/vault/keyfile"
|
||||||
|
remove_keyslot: 4
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
When I(state=opened) returns (generated or given) name
|
When O(state=opened) returns (generated or given) name
|
||||||
of LUKS container. Returns None if no name is supplied.
|
of LUKS container. Returns None if no name is supplied.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
@@ -523,6 +582,29 @@ class CryptHandler(Handler):
|
|||||||
result = self._run_command([self._cryptsetup_bin, 'isLuks', device])
|
result = self._run_command([self._cryptsetup_bin, 'isLuks', device])
|
||||||
return result[RETURN_CODE] == 0
|
return result[RETURN_CODE] == 0
|
||||||
|
|
||||||
|
def get_luks_type(self, device):
|
||||||
|
''' get the luks type of a device
|
||||||
|
'''
|
||||||
|
if self.is_luks(device):
|
||||||
|
with open(device, 'rb') as f:
|
||||||
|
for offset in LUKS2_HEADER_OFFSETS:
|
||||||
|
f.seek(offset)
|
||||||
|
data = f.read(LUKS_HEADER_L)
|
||||||
|
if data == LUKS2_HEADER2:
|
||||||
|
return 'luks2'
|
||||||
|
return 'luks1'
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_luks_slot_set(self, device, keyslot):
|
||||||
|
''' check if a keyslot is set
|
||||||
|
'''
|
||||||
|
result = self._run_command([self._cryptsetup_bin, 'luksDump', device])
|
||||||
|
if result[RETURN_CODE] != 0:
|
||||||
|
raise ValueError('Error while dumping LUKS header from %s' % (device, ))
|
||||||
|
result_luks1 = 'Key Slot %d: ENABLED' % (keyslot) in result[STDOUT]
|
||||||
|
result_luks2 = ' %d: luks2' % (keyslot) in result[STDOUT]
|
||||||
|
return result_luks1 or result_luks2
|
||||||
|
|
||||||
def _add_pbkdf_options(self, options, pbkdf):
|
def _add_pbkdf_options(self, options, pbkdf):
|
||||||
if pbkdf['iteration_time'] is not None:
|
if pbkdf['iteration_time'] is not None:
|
||||||
options.extend(['--iter-time', str(int(pbkdf['iteration_time'] * 1000))])
|
options.extend(['--iter-time', str(int(pbkdf['iteration_time'] * 1000))])
|
||||||
@@ -535,7 +617,7 @@ class CryptHandler(Handler):
|
|||||||
if pbkdf['parallel'] is not None:
|
if pbkdf['parallel'] is not None:
|
||||||
options.extend(['--pbkdf-parallel', str(pbkdf['parallel'])])
|
options.extend(['--pbkdf-parallel', str(pbkdf['parallel'])])
|
||||||
|
|
||||||
def run_luks_create(self, device, keyfile, passphrase, keysize, cipher, hash_, sector_size, pbkdf):
|
def run_luks_create(self, device, keyfile, passphrase, keyslot, keysize, cipher, hash_, sector_size, pbkdf):
|
||||||
# create a new luks container; use batch mode to auto confirm
|
# create a new luks container; use batch mode to auto confirm
|
||||||
luks_type = self._module.params['type']
|
luks_type = self._module.params['type']
|
||||||
label = self._module.params['label']
|
label = self._module.params['label']
|
||||||
@@ -556,6 +638,8 @@ class CryptHandler(Handler):
|
|||||||
self._add_pbkdf_options(options, pbkdf)
|
self._add_pbkdf_options(options, pbkdf)
|
||||||
if sector_size is not None:
|
if sector_size is not None:
|
||||||
options.extend(['--sector-size', str(sector_size)])
|
options.extend(['--sector-size', str(sector_size)])
|
||||||
|
if keyslot is not None:
|
||||||
|
options.extend(['--key-slot', str(keyslot)])
|
||||||
|
|
||||||
args = [self._cryptsetup_bin, 'luksFormat']
|
args = [self._cryptsetup_bin, 'luksFormat']
|
||||||
args.extend(options)
|
args.extend(options)
|
||||||
@@ -569,7 +653,7 @@ class CryptHandler(Handler):
|
|||||||
% (device, result[STDERR]))
|
% (device, result[STDERR]))
|
||||||
|
|
||||||
def run_luks_open(self, device, keyfile, passphrase, perf_same_cpu_crypt, perf_submit_from_crypt_cpus,
|
def run_luks_open(self, device, keyfile, passphrase, perf_same_cpu_crypt, perf_submit_from_crypt_cpus,
|
||||||
perf_no_read_workqueue, perf_no_write_workqueue, persistent, name):
|
perf_no_read_workqueue, perf_no_write_workqueue, persistent, allow_discards, name):
|
||||||
args = [self._cryptsetup_bin]
|
args = [self._cryptsetup_bin]
|
||||||
if keyfile:
|
if keyfile:
|
||||||
args.extend(['--key-file', keyfile])
|
args.extend(['--key-file', keyfile])
|
||||||
@@ -583,6 +667,8 @@ class CryptHandler(Handler):
|
|||||||
args.extend(['--perf-no_write_workqueue'])
|
args.extend(['--perf-no_write_workqueue'])
|
||||||
if persistent:
|
if persistent:
|
||||||
args.extend(['--persistent'])
|
args.extend(['--persistent'])
|
||||||
|
if allow_discards:
|
||||||
|
args.extend(['--allow-discards'])
|
||||||
args.extend(['open', '--type', 'luks', device, name])
|
args.extend(['open', '--type', 'luks', device, name])
|
||||||
|
|
||||||
result = self._run_command(args, data=passphrase)
|
result = self._run_command(args, data=passphrase)
|
||||||
@@ -615,7 +701,7 @@ class CryptHandler(Handler):
|
|||||||
raise ValueError('Error while wiping LUKS container signatures for %s: %s' % (device, exc))
|
raise ValueError('Error while wiping LUKS container signatures for %s: %s' % (device, exc))
|
||||||
|
|
||||||
def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile,
|
def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile,
|
||||||
new_passphrase, pbkdf):
|
new_passphrase, new_keyslot, pbkdf):
|
||||||
''' Add new key from a keyfile or passphrase to given 'device';
|
''' Add new key from a keyfile or passphrase to given 'device';
|
||||||
authentication done using 'keyfile' or 'passphrase'.
|
authentication done using 'keyfile' or 'passphrase'.
|
||||||
Raises ValueError when command fails.
|
Raises ValueError when command fails.
|
||||||
@@ -625,6 +711,9 @@ class CryptHandler(Handler):
|
|||||||
if pbkdf is not None:
|
if pbkdf is not None:
|
||||||
self._add_pbkdf_options(args, pbkdf)
|
self._add_pbkdf_options(args, pbkdf)
|
||||||
|
|
||||||
|
if new_keyslot is not None:
|
||||||
|
args.extend(['--key-slot', str(new_keyslot)])
|
||||||
|
|
||||||
if keyfile:
|
if keyfile:
|
||||||
args.extend(['--key-file', keyfile])
|
args.extend(['--key-file', keyfile])
|
||||||
else:
|
else:
|
||||||
@@ -640,7 +729,7 @@ class CryptHandler(Handler):
|
|||||||
raise ValueError('Error while adding new LUKS keyslot to %s: %s'
|
raise ValueError('Error while adding new LUKS keyslot to %s: %s'
|
||||||
% (device, result[STDERR]))
|
% (device, result[STDERR]))
|
||||||
|
|
||||||
def run_luks_remove_key(self, device, keyfile, passphrase,
|
def run_luks_remove_key(self, device, keyfile, passphrase, keyslot,
|
||||||
force_remove_last_key=False):
|
force_remove_last_key=False):
|
||||||
''' Remove key from given device
|
''' Remove key from given device
|
||||||
Raises ValueError when command fails
|
Raises ValueError when command fails
|
||||||
@@ -675,7 +764,10 @@ class CryptHandler(Handler):
|
|||||||
"To be able to remove a key, please set "
|
"To be able to remove a key, please set "
|
||||||
"`force_remove_last_key` to `true`." % device)
|
"`force_remove_last_key` to `true`." % device)
|
||||||
|
|
||||||
args = [self._cryptsetup_bin, 'luksRemoveKey', device, '-q']
|
if keyslot is None:
|
||||||
|
args = [self._cryptsetup_bin, 'luksRemoveKey', device, '-q']
|
||||||
|
else:
|
||||||
|
args = [self._cryptsetup_bin, 'luksKillSlot', device, '-q', str(keyslot)]
|
||||||
if keyfile:
|
if keyfile:
|
||||||
args.extend(['--key-file', keyfile])
|
args.extend(['--key-file', keyfile])
|
||||||
result = self._run_command(args, data=passphrase)
|
result = self._run_command(args, data=passphrase)
|
||||||
@@ -683,7 +775,7 @@ class CryptHandler(Handler):
|
|||||||
raise ValueError('Error while removing LUKS key from %s: %s'
|
raise ValueError('Error while removing LUKS key from %s: %s'
|
||||||
% (device, result[STDERR]))
|
% (device, result[STDERR]))
|
||||||
|
|
||||||
def luks_test_key(self, device, keyfile, passphrase):
|
def luks_test_key(self, device, keyfile, passphrase, keyslot=None):
|
||||||
''' Check whether the keyfile or passphrase works.
|
''' Check whether the keyfile or passphrase works.
|
||||||
Raises ValueError when command fails.
|
Raises ValueError when command fails.
|
||||||
'''
|
'''
|
||||||
@@ -695,12 +787,22 @@ class CryptHandler(Handler):
|
|||||||
else:
|
else:
|
||||||
data = passphrase
|
data = passphrase
|
||||||
|
|
||||||
|
if keyslot is not None:
|
||||||
|
args.extend(['--key-slot', str(keyslot)])
|
||||||
|
|
||||||
result = self._run_command(args, data=data)
|
result = self._run_command(args, data=data)
|
||||||
if result[RETURN_CODE] == 0:
|
if result[RETURN_CODE] == 0:
|
||||||
return True
|
return True
|
||||||
for output in (STDOUT, STDERR):
|
for output in (STDOUT, STDERR):
|
||||||
if 'No key available with this passphrase' in result[output]:
|
if 'No key available with this passphrase' in result[output]:
|
||||||
return False
|
return False
|
||||||
|
if 'No usable keyslot is available.' in result[output]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# This check is necessary due to cryptsetup in version 2.0.3 not printing 'No usable keyslot is available'
|
||||||
|
# when using the --key-slot parameter in combination with --test-passphrase
|
||||||
|
if result[RETURN_CODE] == 1 and keyslot is not None and result[STDOUT] == '' and result[STDERR] == '':
|
||||||
|
return False
|
||||||
|
|
||||||
raise ValueError('Error while testing whether keyslot exists on %s: %s'
|
raise ValueError('Error while testing whether keyslot exists on %s: %s'
|
||||||
% (device, result[STDERR]))
|
% (device, result[STDERR]))
|
||||||
@@ -812,12 +914,20 @@ class ConditionsHandler(Handler):
|
|||||||
self._module.fail_json(msg="Contradiction in setup: Asking to "
|
self._module.fail_json(msg="Contradiction in setup: Asking to "
|
||||||
"add a key to absent LUKS.")
|
"add a key to absent LUKS.")
|
||||||
|
|
||||||
return not self._crypthandler.luks_test_key(self.device, self._module.params['new_keyfile'], self._module.params['new_passphrase'])
|
key_present = self._crypthandler.luks_test_key(self.device, self._module.params['new_keyfile'], self._module.params['new_passphrase'])
|
||||||
|
if self._module.params['new_keyslot'] is not None:
|
||||||
|
key_present_slot = self._crypthandler.luks_test_key(self.device, self._module.params['new_keyfile'], self._module.params['new_passphrase'],
|
||||||
|
self._module.params['new_keyslot'])
|
||||||
|
if key_present and not key_present_slot:
|
||||||
|
self._module.fail_json(msg="Trying to add key that is already present in another slot")
|
||||||
|
|
||||||
|
return not key_present
|
||||||
|
|
||||||
def luks_remove_key(self):
|
def luks_remove_key(self):
|
||||||
if (self.device is None or
|
if (self.device is None or
|
||||||
(self._module.params['remove_keyfile'] is None and
|
(self._module.params['remove_keyfile'] is None and
|
||||||
self._module.params['remove_passphrase'] is None)):
|
self._module.params['remove_passphrase'] is None and
|
||||||
|
self._module.params['remove_keyslot'] is None)):
|
||||||
# conditions for removing a key not fulfilled
|
# conditions for removing a key not fulfilled
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -825,6 +935,15 @@ class ConditionsHandler(Handler):
|
|||||||
self._module.fail_json(msg="Contradiction in setup: Asking to "
|
self._module.fail_json(msg="Contradiction in setup: Asking to "
|
||||||
"remove a key from absent LUKS.")
|
"remove a key from absent LUKS.")
|
||||||
|
|
||||||
|
if self._module.params['remove_keyslot'] is not None:
|
||||||
|
if not self._crypthandler.is_luks_slot_set(self.device, self._module.params['remove_keyslot']):
|
||||||
|
return False
|
||||||
|
result = self._crypthandler.luks_test_key(self.device, self._module.params['keyfile'], self._module.params['passphrase'])
|
||||||
|
if self._crypthandler.luks_test_key(self.device, self._module.params['keyfile'], self._module.params['passphrase'],
|
||||||
|
self._module.params['remove_keyslot']):
|
||||||
|
self._module.fail_json(msg='Cannot remove keyslot with keyfile or passphrase in same slot.')
|
||||||
|
return result
|
||||||
|
|
||||||
return self._crypthandler.luks_test_key(self.device, self._module.params['remove_keyfile'], self._module.params['remove_passphrase'])
|
return self._crypthandler.luks_test_key(self.device, self._module.params['remove_keyfile'], self._module.params['remove_passphrase'])
|
||||||
|
|
||||||
def luks_remove(self):
|
def luks_remove(self):
|
||||||
@@ -832,6 +951,19 @@ class ConditionsHandler(Handler):
|
|||||||
self._module.params['state'] == 'absent' and
|
self._module.params['state'] == 'absent' and
|
||||||
self._crypthandler.is_luks(self.device))
|
self._crypthandler.is_luks(self.device))
|
||||||
|
|
||||||
|
def validate_keyslot(self, param, luks_type):
|
||||||
|
if self._module.params[param] is not None:
|
||||||
|
if luks_type is None and param == 'keyslot':
|
||||||
|
if 8 <= self._module.params[param] <= 31:
|
||||||
|
self._module.fail_json(msg="You must specify type=luks2 when creating a new LUKS device to use keyslots 8-31.")
|
||||||
|
elif not (0 <= self._module.params[param] <= 7):
|
||||||
|
self._module.fail_json(msg="When not specifying a type, only the keyslots 0-7 are allowed.")
|
||||||
|
|
||||||
|
if luks_type == 'luks1' and not 0 <= self._module.params[param] <= 7:
|
||||||
|
self._module.fail_json(msg="%s must be between 0 and 7 when using LUKS1." % self._module.params[param])
|
||||||
|
elif luks_type == 'luks2' and not 0 <= self._module.params[param] <= 31:
|
||||||
|
self._module.fail_json(msg="%s must be between 0 and 31 when using LUKS2." % self._module.params[param])
|
||||||
|
|
||||||
|
|
||||||
def run_module():
|
def run_module():
|
||||||
# available arguments/parameters that a user can pass
|
# available arguments/parameters that a user can pass
|
||||||
@@ -845,6 +977,9 @@ def run_module():
|
|||||||
passphrase=dict(type='str', no_log=True),
|
passphrase=dict(type='str', no_log=True),
|
||||||
new_passphrase=dict(type='str', no_log=True),
|
new_passphrase=dict(type='str', no_log=True),
|
||||||
remove_passphrase=dict(type='str', no_log=True),
|
remove_passphrase=dict(type='str', no_log=True),
|
||||||
|
keyslot=dict(type='int', no_log=False),
|
||||||
|
new_keyslot=dict(type='int', no_log=False),
|
||||||
|
remove_keyslot=dict(type='int', no_log=False),
|
||||||
force_remove_last_key=dict(type='bool', default=False),
|
force_remove_last_key=dict(type='bool', default=False),
|
||||||
keysize=dict(type='int'),
|
keysize=dict(type='int'),
|
||||||
label=dict(type='str'),
|
label=dict(type='str'),
|
||||||
@@ -869,12 +1004,13 @@ def run_module():
|
|||||||
perf_no_read_workqueue=dict(type='bool', default=False),
|
perf_no_read_workqueue=dict(type='bool', default=False),
|
||||||
perf_no_write_workqueue=dict(type='bool', default=False),
|
perf_no_write_workqueue=dict(type='bool', default=False),
|
||||||
persistent=dict(type='bool', default=False),
|
persistent=dict(type='bool', default=False),
|
||||||
|
allow_discards=dict(type='bool', default=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
mutually_exclusive = [
|
mutually_exclusive = [
|
||||||
('keyfile', 'passphrase'),
|
('keyfile', 'passphrase'),
|
||||||
('new_keyfile', 'new_passphrase'),
|
('new_keyfile', 'new_passphrase'),
|
||||||
('remove_keyfile', 'remove_passphrase')
|
('remove_keyfile', 'remove_passphrase', 'remove_keyslot')
|
||||||
]
|
]
|
||||||
|
|
||||||
# seed the result dict in the object
|
# seed the result dict in the object
|
||||||
@@ -904,6 +1040,17 @@ def run_module():
|
|||||||
if module.params['label'] is not None and module.params['type'] == 'luks1':
|
if module.params['label'] is not None and module.params['type'] == 'luks1':
|
||||||
module.fail_json(msg='You cannot combine type luks1 with the label option.')
|
module.fail_json(msg='You cannot combine type luks1 with the label option.')
|
||||||
|
|
||||||
|
if module.params['keyslot'] is not None or module.params['new_keyslot'] is not None or module.params['remove_keyslot'] is not None:
|
||||||
|
luks_type = crypt.get_luks_type(conditions.get_device_name())
|
||||||
|
if luks_type is None and module.params['type'] is not None:
|
||||||
|
luks_type = module.params['type']
|
||||||
|
for param in ['keyslot', 'new_keyslot', 'remove_keyslot']:
|
||||||
|
conditions.validate_keyslot(param, luks_type)
|
||||||
|
|
||||||
|
for param in ['new_keyslot', 'remove_keyslot']:
|
||||||
|
if module.params[param] is not None and module.params['keyfile'] is None and module.params['passphrase'] is None:
|
||||||
|
module.fail_json(msg="Removing a keyslot requires the passphrase or keyfile of another slot.")
|
||||||
|
|
||||||
# The conditions are in order to allow more operations in one run.
|
# The conditions are in order to allow more operations in one run.
|
||||||
# (e.g. create luks and add a key to it)
|
# (e.g. create luks and add a key to it)
|
||||||
|
|
||||||
@@ -914,6 +1061,7 @@ def run_module():
|
|||||||
crypt.run_luks_create(conditions.device,
|
crypt.run_luks_create(conditions.device,
|
||||||
module.params['keyfile'],
|
module.params['keyfile'],
|
||||||
module.params['passphrase'],
|
module.params['passphrase'],
|
||||||
|
module.params['keyslot'],
|
||||||
module.params['keysize'],
|
module.params['keysize'],
|
||||||
module.params['cipher'],
|
module.params['cipher'],
|
||||||
module.params['hash'],
|
module.params['hash'],
|
||||||
@@ -949,6 +1097,7 @@ def run_module():
|
|||||||
module.params['perf_no_read_workqueue'],
|
module.params['perf_no_read_workqueue'],
|
||||||
module.params['perf_no_write_workqueue'],
|
module.params['perf_no_write_workqueue'],
|
||||||
module.params['persistent'],
|
module.params['persistent'],
|
||||||
|
module.params['allow_discards'],
|
||||||
name)
|
name)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
module.fail_json(msg="luks_device error: %s" % e)
|
module.fail_json(msg="luks_device error: %s" % e)
|
||||||
@@ -986,6 +1135,7 @@ def run_module():
|
|||||||
module.params['passphrase'],
|
module.params['passphrase'],
|
||||||
module.params['new_keyfile'],
|
module.params['new_keyfile'],
|
||||||
module.params['new_passphrase'],
|
module.params['new_passphrase'],
|
||||||
|
module.params['new_keyslot'],
|
||||||
module.params['pbkdf'])
|
module.params['pbkdf'])
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
module.fail_json(msg="luks_device error: %s" % e)
|
module.fail_json(msg="luks_device error: %s" % e)
|
||||||
@@ -1001,6 +1151,7 @@ def run_module():
|
|||||||
crypt.run_luks_remove_key(conditions.device,
|
crypt.run_luks_remove_key(conditions.device,
|
||||||
module.params['remove_keyfile'],
|
module.params['remove_keyfile'],
|
||||||
module.params['remove_passphrase'],
|
module.params['remove_passphrase'],
|
||||||
|
module.params['remove_keyslot'],
|
||||||
force_remove_last_key=last_key)
|
force_remove_last_key=last_key)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
module.fail_json(msg="luks_device error: %s" % e)
|
module.fail_json(msg="luks_device error: %s" % e)
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ options:
|
|||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- Whether the module should generate a host or a user certificate.
|
- Whether the module should generate a host or a user certificate.
|
||||||
- Required if I(state) is C(present).
|
- Required if O(state) is V(present).
|
||||||
type: str
|
type: str
|
||||||
choices: ['host', 'user']
|
choices: ['host', 'user']
|
||||||
force:
|
force:
|
||||||
description:
|
description:
|
||||||
- Should the certificate be regenerated even if it already exists and is valid.
|
- Should the certificate be regenerated even if it already exists and is valid.
|
||||||
- Equivalent to I(regenerate=always).
|
- Equivalent to O(regenerate=always).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
path:
|
path:
|
||||||
@@ -56,16 +56,16 @@ options:
|
|||||||
required: true
|
required: true
|
||||||
regenerate:
|
regenerate:
|
||||||
description:
|
description:
|
||||||
- When C(never) the task will fail if a certificate already exists at I(path) and is unreadable
|
- When V(never) the task will fail if a certificate already exists at O(path) and is unreadable
|
||||||
otherwise a new certificate will only be generated if there is no existing certificate.
|
otherwise a new certificate will only be generated if there is no existing certificate.
|
||||||
- When C(fail) the task will fail if a certificate already exists at I(path) and does not
|
- When V(fail) the task will fail if a certificate already exists at O(path) and does not
|
||||||
match the module's options.
|
match the module's options.
|
||||||
- When C(partial_idempotence) an existing certificate will be regenerated based on
|
- When V(partial_idempotence) an existing certificate will be regenerated based on
|
||||||
I(serial), I(signature_algorithm), I(type), I(valid_from), I(valid_to), I(valid_at), and I(principals).
|
O(serial_number), O(signature_algorithm), O(type), O(valid_from), O(valid_to), O(valid_at), and O(principals).
|
||||||
I(valid_from) and I(valid_to) can be excluded by I(ignore_timestamps=true).
|
O(valid_from) and O(valid_to) can be excluded by O(ignore_timestamps=true).
|
||||||
- When C(full_idempotence) I(identifier), I(options), I(public_key), and I(signing_key)
|
- When V(full_idempotence) O(identifier), O(options), O(public_key), and O(signing_key)
|
||||||
are also considered when compared against an existing certificate.
|
are also considered when compared against an existing certificate.
|
||||||
- C(always) is equivalent to I(force=true).
|
- V(always) is equivalent to O(force=true).
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
- never
|
- never
|
||||||
@@ -78,14 +78,14 @@ options:
|
|||||||
signature_algorithm:
|
signature_algorithm:
|
||||||
description:
|
description:
|
||||||
- As of OpenSSH 8.2 the SHA-1 signature algorithm for RSA keys has been disabled and C(ssh) will refuse
|
- As of OpenSSH 8.2 the SHA-1 signature algorithm for RSA keys has been disabled and C(ssh) will refuse
|
||||||
host certificates signed with the SHA-1 algorithm. OpenSSH 8.1 made C(rsa-sha2-512) the default algorithm
|
host certificates signed with the SHA-1 algorithm. OpenSSH 8.1 made V(rsa-sha2-512) the default algorithm
|
||||||
when acting as a CA and signing certificates with a RSA key. However, for OpenSSH versions less than 8.1
|
when acting as a CA and signing certificates with a RSA key. However, for OpenSSH versions less than 8.1
|
||||||
the SHA-2 signature algorithms, C(rsa-sha2-256) or C(rsa-sha2-512), must be specified using this option
|
the SHA-2 signature algorithms, V(rsa-sha2-256) or V(rsa-sha2-512), must be specified using this option
|
||||||
if compatibility with newer C(ssh) clients is required. Conversely if hosts using OpenSSH version 8.2
|
if compatibility with newer C(ssh) clients is required. Conversely if hosts using OpenSSH version 8.2
|
||||||
or greater must remain compatible with C(ssh) clients using OpenSSH less than 7.2, then C(ssh-rsa)
|
or greater must remain compatible with C(ssh) clients using OpenSSH less than 7.2, then V(ssh-rsa)
|
||||||
can be used when generating host certificates (a corresponding change to the sshd_config to add C(ssh-rsa)
|
can be used when generating host certificates (a corresponding change to the sshd_config to add V(ssh-rsa)
|
||||||
to the C(CASignatureAlgorithms) keyword is also required).
|
to the C(CASignatureAlgorithms) keyword is also required).
|
||||||
- Using any value for this option with a non-RSA I(signing_key) will cause this module to fail.
|
- Using any value for this option with a non-RSA O(signing_key) will cause this module to fail.
|
||||||
- "Note: OpenSSH versions prior to 7.2 do not support SHA-2 signature algorithms for RSA keys and OpenSSH
|
- "Note: OpenSSH versions prior to 7.2 do not support SHA-2 signature algorithms for RSA keys and OpenSSH
|
||||||
versions prior to 7.3 do not support SHA-2 signature algorithms for certificates."
|
versions prior to 7.3 do not support SHA-2 signature algorithms for certificates."
|
||||||
- See U(https://www.openssh.com/txt/release-8.2) for more information.
|
- See U(https://www.openssh.com/txt/release-8.2) for more information.
|
||||||
@@ -98,14 +98,14 @@ options:
|
|||||||
signing_key:
|
signing_key:
|
||||||
description:
|
description:
|
||||||
- The path to the private openssh key that is used for signing the public key in order to generate the certificate.
|
- The path to the private openssh key that is used for signing the public key in order to generate the certificate.
|
||||||
- If the private key is on a PKCS#11 token (I(pkcs11_provider)), set this to the path to the public key instead.
|
- If the private key is on a PKCS#11 token (O(pkcs11_provider)), set this to the path to the public key instead.
|
||||||
- Required if I(state) is C(present).
|
- Required if O(state) is V(present).
|
||||||
type: path
|
type: path
|
||||||
pkcs11_provider:
|
pkcs11_provider:
|
||||||
description:
|
description:
|
||||||
- To use a signing key that resides on a PKCS#11 token, set this to the name (or full path) of the shared library to use with the token.
|
- To use a signing key that resides on a PKCS#11 token, set this to the name (or full path) of the shared library to use with the token.
|
||||||
Usually C(libpkcs11.so).
|
Usually C(libpkcs11.so).
|
||||||
- If this is set, I(signing_key) needs to point to a file containing the public key of the CA.
|
- If this is set, O(signing_key) needs to point to a file containing the public key of the CA.
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.1.0
|
version_added: 1.1.0
|
||||||
use_agent:
|
use_agent:
|
||||||
@@ -117,37 +117,37 @@ options:
|
|||||||
public_key:
|
public_key:
|
||||||
description:
|
description:
|
||||||
- The path to the public key that will be signed with the signing key in order to generate the certificate.
|
- The path to the public key that will be signed with the signing key in order to generate the certificate.
|
||||||
- Required if I(state) is C(present).
|
- Required if O(state) is V(present).
|
||||||
type: path
|
type: path
|
||||||
valid_from:
|
valid_from:
|
||||||
description:
|
description:
|
||||||
- "The point in time the certificate is valid from. Time can be specified either as relative time or as absolute timestamp.
|
- "The point in time the certificate is valid from. Time can be specified either as relative time or as absolute timestamp.
|
||||||
Time will always be interpreted as UTC. Valid formats are: C([+-]timespec | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS | YYYY-MM-DD HH:MM:SS | always)
|
Time will always be interpreted as UTC. Valid formats are: C([+-]timespec | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS | YYYY-MM-DD HH:MM:SS | always)
|
||||||
where timespec can be an integer + C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
where timespec can be an integer + C([w | d | h | m | s]) (for example V(+32w1d2h)).
|
||||||
Note that if using relative time this module is NOT idempotent."
|
Note that if using relative time this module is NOT idempotent."
|
||||||
- "The value C(always) is only supported for OpenSSH 7.7 and greater, however, the value C(1970-01-01T00:00:01)
|
- "The value V(always) is only supported for OpenSSH 7.7 and greater, however, the value V(1970-01-01T00:00:01)
|
||||||
can be used with earlier versions as an equivalent expression."
|
can be used with earlier versions as an equivalent expression."
|
||||||
- "To ignore this value during comparison with an existing certificate set I(ignore_timestamps=true)."
|
- "To ignore this value during comparison with an existing certificate set O(ignore_timestamps=true)."
|
||||||
- Required if I(state) is C(present).
|
- Required if O(state) is V(present).
|
||||||
type: str
|
type: str
|
||||||
valid_to:
|
valid_to:
|
||||||
description:
|
description:
|
||||||
- "The point in time the certificate is valid to. Time can be specified either as relative time or as absolute timestamp.
|
- "The point in time the certificate is valid to. Time can be specified either as relative time or as absolute timestamp.
|
||||||
Time will always be interpreted as UTC. Valid formats are: C([+-]timespec | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS | YYYY-MM-DD HH:MM:SS | forever)
|
Time will always be interpreted as UTC. Valid formats are: C([+-]timespec | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS | YYYY-MM-DD HH:MM:SS | forever)
|
||||||
where timespec can be an integer + C([w | d | h | m | s]) (for example C(+32w1d2h)).
|
where timespec can be an integer + C([w | d | h | m | s]) (for example V(+32w1d2h)).
|
||||||
Note that if using relative time this module is NOT idempotent."
|
Note that if using relative time this module is NOT idempotent."
|
||||||
- "To ignore this value during comparison with an existing certificate set I(ignore_timestamps=true)."
|
- "To ignore this value during comparison with an existing certificate set O(ignore_timestamps=true)."
|
||||||
- Required if I(state) is C(present).
|
- Required if O(state) is V(present).
|
||||||
type: str
|
type: str
|
||||||
valid_at:
|
valid_at:
|
||||||
description:
|
description:
|
||||||
- "Check if the certificate is valid at a certain point in time. If it is not the certificate will be regenerated.
|
- "Check if the certificate is valid at a certain point in time. If it is not the certificate will be regenerated.
|
||||||
Time will always be interpreted as UTC. Mainly to be used with relative timespec for I(valid_from) and / or I(valid_to).
|
Time will always be interpreted as UTC. Mainly to be used with relative timespec for O(valid_from) and / or O(valid_to).
|
||||||
Note that if using relative time this module is NOT idempotent."
|
Note that if using relative time this module is NOT idempotent."
|
||||||
type: str
|
type: str
|
||||||
ignore_timestamps:
|
ignore_timestamps:
|
||||||
description:
|
description:
|
||||||
- "Whether the I(valid_from) and I(valid_to) timestamps should be ignored for idempotency checks."
|
- "Whether the O(valid_from) and O(valid_to) timestamps should be ignored for idempotency checks."
|
||||||
- "However, the values will still be applied to a new certificate if it meets any other necessary conditions for generation/regeneration."
|
- "However, the values will still be applied to a new certificate if it meets any other necessary conditions for generation/regeneration."
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
@@ -161,20 +161,20 @@ options:
|
|||||||
options:
|
options:
|
||||||
description:
|
description:
|
||||||
- "Specify certificate options when signing a key. The option that are valid for user certificates are:"
|
- "Specify certificate options when signing a key. The option that are valid for user certificates are:"
|
||||||
- "C(clear): Clear all enabled permissions. This is useful for clearing the default set of permissions so permissions may be added individually."
|
- "V(clear): Clear all enabled permissions. This is useful for clearing the default set of permissions so permissions may be added individually."
|
||||||
- "C(force-command=command): Forces the execution of command instead of any shell or
|
- "V(force-command=command): Forces the execution of command instead of any shell or
|
||||||
command specified by the user when the certificate is used for authentication."
|
command specified by the user when the certificate is used for authentication."
|
||||||
- "C(no-agent-forwarding): Disable ssh-agent forwarding (permitted by default)."
|
- "V(no-agent-forwarding): Disable ssh-agent forwarding (permitted by default)."
|
||||||
- "C(no-port-forwarding): Disable port forwarding (permitted by default)."
|
- "V(no-port-forwarding): Disable port forwarding (permitted by default)."
|
||||||
- "C(no-pty): Disable PTY allocation (permitted by default)."
|
- "V(no-pty): Disable PTY allocation (permitted by default)."
|
||||||
- "C(no-user-rc): Disable execution of C(~/.ssh/rc) by sshd (permitted by default)."
|
- "V(no-user-rc): Disable execution of C(~/.ssh/rc) by sshd (permitted by default)."
|
||||||
- "C(no-x11-forwarding): Disable X11 forwarding (permitted by default)"
|
- "V(no-x11-forwarding): Disable X11 forwarding (permitted by default)"
|
||||||
- "C(permit-agent-forwarding): Allows ssh-agent forwarding."
|
- "V(permit-agent-forwarding): Allows ssh-agent forwarding."
|
||||||
- "C(permit-port-forwarding): Allows port forwarding."
|
- "V(permit-port-forwarding): Allows port forwarding."
|
||||||
- "C(permit-pty): Allows PTY allocation."
|
- "V(permit-pty): Allows PTY allocation."
|
||||||
- "C(permit-user-rc): Allows execution of C(~/.ssh/rc) by sshd."
|
- "V(permit-user-rc): Allows execution of C(~/.ssh/rc) by sshd."
|
||||||
- "C(permit-x11-forwarding): Allows X11 forwarding."
|
- "V(permit-x11-forwarding): Allows X11 forwarding."
|
||||||
- "C(source-address=address_list): Restrict the source addresses from which the certificate is considered valid.
|
- "V(source-address=address_list): Restrict the source addresses from which the certificate is considered valid.
|
||||||
The C(address_list) is a comma-separated list of one or more address/netmask pairs in CIDR format."
|
The C(address_list) is a comma-separated list of one or more address/netmask pairs in CIDR format."
|
||||||
- "At present, no options are valid for host keys."
|
- "At present, no options are valid for host keys."
|
||||||
type: list
|
type: list
|
||||||
@@ -190,7 +190,13 @@ options:
|
|||||||
The certificate serial number may be used in a KeyRevocationList.
|
The certificate serial number may be used in a KeyRevocationList.
|
||||||
The serial number may be omitted for checks, but must be specified again for a new certificate.
|
The serial number may be omitted for checks, but must be specified again for a new certificate.
|
||||||
Note: The default value set by ssh-keygen is 0."
|
Note: The default value set by ssh-keygen is 0."
|
||||||
|
- This option accepts an B(integer). If you want to provide serial numbers as colon-separated hex strings,
|
||||||
|
such as C(11:22:33), you need to convert them to an integer with P(community.crypto.parse_serial#filter).
|
||||||
type: int
|
type: int
|
||||||
|
|
||||||
|
seealso:
|
||||||
|
- plugin: community.crypto.parse_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
@@ -497,7 +503,10 @@ class Certificate(OpensshModule):
|
|||||||
if self.state != 'present':
|
if self.state != 'present':
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
certificate_info = self.ssh_keygen.get_certificate_info(self.path)[1]
|
certificate_info = self.ssh_keygen.get_certificate_info(
|
||||||
|
self.path,
|
||||||
|
check_rc=self.state == 'present' and not self.module.check_mode,
|
||||||
|
)[1]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'type': self.type,
|
'type': self.type,
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ author: "David Kainz (@lolcube)"
|
|||||||
short_description: Generate OpenSSH private and public keys
|
short_description: Generate OpenSSH private and public keys
|
||||||
description:
|
description:
|
||||||
- "This module allows one to (re)generate OpenSSH private and public keys. It uses
|
- "This module allows one to (re)generate OpenSSH private and public keys. It uses
|
||||||
ssh-keygen to generate keys. One can generate C(rsa), C(dsa), C(rsa1), C(ed25519)
|
ssh-keygen to generate keys. One can generate V(rsa), V(dsa), V(rsa1), V(ed25519)
|
||||||
or C(ecdsa) private keys."
|
or V(ecdsa) private keys."
|
||||||
requirements:
|
requirements:
|
||||||
- ssh-keygen (if I(backend=openssh))
|
- ssh-keygen (if O(backend=openssh))
|
||||||
- cryptography >= 2.6 (if I(backend=cryptography) and OpenSSH < 7.8 is installed)
|
- cryptography >= 2.6 (if O(backend=cryptography) and OpenSSH < 7.8 is installed)
|
||||||
- cryptography >= 3.0 (if I(backend=cryptography) and OpenSSH >= 7.8 is installed)
|
- cryptography >= 3.0 (if O(backend=cryptography) and OpenSSH >= 7.8 is installed)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- ansible.builtin.files
|
- ansible.builtin.files
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
@@ -49,8 +49,8 @@ options:
|
|||||||
type: int
|
type: int
|
||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- "The algorithm used to generate the SSH private key. C(rsa1) is for protocol version 1.
|
- "The algorithm used to generate the SSH private key. V(rsa1) is for protocol version 1.
|
||||||
C(rsa1) is deprecated and may not be supported by every version of ssh-keygen."
|
V(rsa1) is deprecated and may not be supported by every version of ssh-keygen."
|
||||||
type: str
|
type: str
|
||||||
default: rsa
|
default: rsa
|
||||||
choices: ['rsa', 'dsa', 'rsa1', 'ecdsa', 'ed25519']
|
choices: ['rsa', 'dsa', 'rsa1', 'ecdsa', 'ed25519']
|
||||||
@@ -71,18 +71,18 @@ options:
|
|||||||
passphrase:
|
passphrase:
|
||||||
description:
|
description:
|
||||||
- Passphrase used to decrypt an existing private key or encrypt a newly generated private key.
|
- Passphrase used to decrypt an existing private key or encrypt a newly generated private key.
|
||||||
- Passphrases are not supported for I(type=rsa1).
|
- Passphrases are not supported for O(type=rsa1).
|
||||||
- Can only be used when I(backend=cryptography), or when I(backend=auto) and a required C(cryptography) version is installed.
|
- Can only be used when O(backend=cryptography), or when O(backend=auto) and a required C(cryptography) version is installed.
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.7.0
|
version_added: 1.7.0
|
||||||
private_key_format:
|
private_key_format:
|
||||||
description:
|
description:
|
||||||
- Used when I(backend=cryptography) to select a format for the private key at the provided I(path).
|
- Used when O(backend=cryptography) to select a format for the private key at the provided O(path).
|
||||||
- When set to C(auto) this module will match the key format of the installed OpenSSH version.
|
- When set to V(auto) this module will match the key format of the installed OpenSSH version.
|
||||||
- For OpenSSH < 7.8 private keys will be in PKCS1 format except ed25519 keys which will be in OpenSSH format.
|
- For OpenSSH < 7.8 private keys will be in PKCS1 format except ed25519 keys which will be in OpenSSH format.
|
||||||
- For OpenSSH >= 7.8 all private key types will be in the OpenSSH format.
|
- For OpenSSH >= 7.8 all private key types will be in the OpenSSH format.
|
||||||
- Using this option when I(regenerate=partial_idempotence) or I(regenerate=full_idempotence) will cause
|
- Using this option when O(regenerate=partial_idempotence) or O(regenerate=full_idempotence) will cause
|
||||||
a new keypair to be generated if the private key's format does not match the value of I(private_key_format).
|
a new keypair to be generated if the private key's format does not match the value of O(private_key_format).
|
||||||
This module will not however convert existing private keys between formats.
|
This module will not however convert existing private keys between formats.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
@@ -94,8 +94,8 @@ options:
|
|||||||
version_added: 1.7.0
|
version_added: 1.7.0
|
||||||
backend:
|
backend:
|
||||||
description:
|
description:
|
||||||
- Selects between the C(cryptography) library or the OpenSSH binary C(opensshbin).
|
- Selects between the V(cryptography) library or the OpenSSH binary V(opensshbin).
|
||||||
- C(auto) will default to C(opensshbin) unless the OpenSSH binary is not installed or when using I(passphrase).
|
- V(auto) will default to V(opensshbin) unless the OpenSSH binary is not installed or when using O(passphrase).
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices:
|
choices:
|
||||||
@@ -109,24 +109,24 @@ options:
|
|||||||
The module will always generate a new key if the destination file does not exist.
|
The module will always generate a new key if the destination file does not exist.
|
||||||
- By default, the key will be regenerated when it does not 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
|
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)
|
this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if V(full_idempotence)
|
||||||
is specified.
|
is specified.
|
||||||
- If set to C(never), the module will fail if the key cannot be read or the passphrase
|
- If set to V(never), the module will fail if the key cannot be read or the passphrase
|
||||||
is not 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
|
- If set to V(fail), the module will fail if the key does not correspond to the module's
|
||||||
options.
|
options.
|
||||||
- If set to C(partial_idempotence), the key will be regenerated if it does not conform to
|
- If set to V(partial_idempotence), the key will be regenerated if it does not conform to
|
||||||
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
|
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
|
||||||
the key is protected by an unknown passphrase, or when they key is not protected by a
|
the key is protected by an unknown passphrase, or when they key is not protected by a
|
||||||
passphrase, but a passphrase is specified.
|
passphrase, but a passphrase is specified.
|
||||||
- If set to C(full_idempotence), the key will be regenerated if it does not conform to the
|
- If set to V(full_idempotence), the key will be regenerated if it does not conform to the
|
||||||
module's options. This is also the case if the key cannot be read (broken file), the key
|
module's options. This is also the case if the key cannot be read (broken file), the key
|
||||||
is protected by an unknown passphrase, or when they key is not protected by a passphrase,
|
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!
|
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
|
- If set to V(always), the module will always regenerate the key. This is equivalent to
|
||||||
setting I(force) to C(true).
|
setting O(force) to V(true).
|
||||||
- Note that adjusting the comment and the permissions can be changed without regeneration.
|
- Note that adjusting the comment and the permissions can be changed without regeneration.
|
||||||
Therefore, even for C(never), the task can result in changed.
|
Therefore, even for V(never), the task can result in changed.
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
- never
|
- never
|
||||||
@@ -138,8 +138,8 @@ options:
|
|||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
notes:
|
notes:
|
||||||
- In case the ssh key is broken or password protected, the module will fail.
|
- In case the ssh key is broken or password protected, the module will fail.
|
||||||
Set the I(force) option to C(true) if you want to regenerate the keypair.
|
Set the O(force) option to V(true) if you want to regenerate the keypair.
|
||||||
- In the case a custom C(mode), C(group), C(owner), or other file attribute is provided it will be applied to both key files.
|
- In the case a custom O(mode), O(group), O(owner), or other file attribute is provided it will be applied to both key files.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ short_description: Generate OpenSSL Certificate Signing Request (CSR)
|
|||||||
description:
|
description:
|
||||||
- "Please note that the module regenerates an existing CSR if it does not match the module's
|
- "Please note that the module regenerates an existing CSR if it does not match the module's
|
||||||
options, or if it seems to be corrupt. If you are concerned that this could overwrite
|
options, or if it seems to be corrupt. If you are concerned that this could overwrite
|
||||||
your existing CSR, consider using the I(backup) option."
|
your existing CSR, consider using the O(backup) option."
|
||||||
author:
|
author:
|
||||||
- Yanis Guenane (@Spredzy)
|
- Yanis Guenane (@Spredzy)
|
||||||
- Felix Fontein (@felixfontein)
|
- Felix Fontein (@felixfontein)
|
||||||
@@ -58,7 +58,7 @@ options:
|
|||||||
default: false
|
default: false
|
||||||
return_content:
|
return_content:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), will return the (current or generated) CSR's content as I(csr).
|
- If set to V(true), will return the (current or generated) CSR's content as RV(csr).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
@@ -173,7 +173,7 @@ RETURN = r'''
|
|||||||
privatekey:
|
privatekey:
|
||||||
description:
|
description:
|
||||||
- Path to the TLS/SSL private key the CSR was generated for
|
- Path to the TLS/SSL private key the CSR was generated for
|
||||||
- Will be C(none) if the private key has been provided in I(privatekey_content).
|
- Will be V(none) if the private key has been provided in O(privatekey_content).
|
||||||
returned: changed or success
|
returned: changed or success
|
||||||
type: str
|
type: str
|
||||||
sample: /etc/ssl/private/ansible.com.pem
|
sample: /etc/ssl/private/ansible.com.pem
|
||||||
@@ -234,12 +234,12 @@ name_constraints_excluded:
|
|||||||
version_added: 1.1.0
|
version_added: 1.1.0
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created.
|
description: Name of backup file created.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/www.ansible.com.csr.2019-03-09@11:22~
|
sample: /path/to/www.ansible.com.csr.2019-03-09@11:22~
|
||||||
csr:
|
csr:
|
||||||
description: The (current or generated) CSR's content.
|
description: The (current or generated) CSR's content.
|
||||||
returned: if I(state) is C(present) and I(return_content) is C(true)
|
returned: if O(state) is V(present) and O(return_content) is V(true)
|
||||||
type: str
|
type: str
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ options:
|
|||||||
path:
|
path:
|
||||||
description:
|
description:
|
||||||
- Remote absolute path where the CSR file is loaded from.
|
- Remote absolute path where the CSR file is loaded from.
|
||||||
- Either I(path) or I(content) must be specified, but not both.
|
- Either O(path) or O(content) must be specified, but not both.
|
||||||
type: path
|
type: path
|
||||||
content:
|
content:
|
||||||
description:
|
description:
|
||||||
- Content of the CSR file.
|
- Content of the CSR file.
|
||||||
- Either I(path) or I(content) must be specified, but not both.
|
- Either O(path) or O(content) must be specified, but not both.
|
||||||
type: str
|
type: str
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -52,10 +52,11 @@ options:
|
|||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.openssl_csr
|
- module: community.crypto.openssl_csr
|
||||||
- module: community.crypto.openssl_csr_pipe
|
- module: community.crypto.openssl_csr_pipe
|
||||||
- ref: community.crypto.openssl_csr_info filter <ansible_collections.community.crypto.openssl_csr_info_filter>
|
- plugin: community.crypto.openssl_csr_info
|
||||||
# - plugin: community.crypto.openssl_csr_info
|
plugin_type: filter
|
||||||
# plugin_type: filter
|
|
||||||
description: A filter variant of this module.
|
description: A filter variant of this module.
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
@@ -71,7 +72,7 @@ EXAMPLES = r'''
|
|||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Dump information
|
- name: Dump information
|
||||||
debug:
|
ansible.builtin.debug:
|
||||||
var: result
|
var: result
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -79,11 +80,11 @@ RETURN = r'''
|
|||||||
signature_valid:
|
signature_valid:
|
||||||
description:
|
description:
|
||||||
- Whether the CSR's signature is valid.
|
- Whether the CSR's signature is valid.
|
||||||
- In case the check returns C(false), the module will fail.
|
- In case the check returns V(false), the module will fail.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
basic_constraints:
|
basic_constraints:
|
||||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
description: Entries in the C(basic_constraints) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -93,7 +94,7 @@ basic_constraints_critical:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
extended_key_usage:
|
extended_key_usage:
|
||||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(extended_key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -124,7 +125,7 @@ extensions_by_oid:
|
|||||||
sample: "MAMCAQU="
|
sample: "MAMCAQU="
|
||||||
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
||||||
key_usage:
|
key_usage:
|
||||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: [Key Agreement, Data Encipherment]
|
sample: [Key Agreement, Data Encipherment]
|
||||||
@@ -134,8 +135,8 @@ key_usage_critical:
|
|||||||
type: bool
|
type: bool
|
||||||
subject_alt_name:
|
subject_alt_name:
|
||||||
description:
|
description:
|
||||||
- Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
- Entries in the C(subject_alt_name) extension, or V(none) if extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -145,7 +146,7 @@ subject_alt_name_critical:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple:
|
ocsp_must_staple:
|
||||||
description: C(true) if the OCSP Must Staple extension is present, C(none) otherwise.
|
description: V(true) if the OCSP Must Staple extension is present, V(none) otherwise.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple_critical:
|
ocsp_must_staple_critical:
|
||||||
@@ -162,8 +163,8 @@ name_constraints_permitted:
|
|||||||
name_constraints_excluded:
|
name_constraints_excluded:
|
||||||
description:
|
description:
|
||||||
- List of excluded subtrees the CA cannot sign certificates for.
|
- List of excluded subtrees the CA cannot sign certificates for.
|
||||||
- Is C(none) if extension is not present.
|
- Is V(none) if extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -172,7 +173,7 @@ name_constraints_excluded:
|
|||||||
name_constraints_critical:
|
name_constraints_critical:
|
||||||
description:
|
description:
|
||||||
- Whether the C(name_constraints) extension is critical.
|
- Whether the C(name_constraints) extension is critical.
|
||||||
- Is C(none) if extension is not present.
|
- Is V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
version_added: 1.1.0
|
version_added: 1.1.0
|
||||||
@@ -197,8 +198,8 @@ public_key:
|
|||||||
public_key_type:
|
public_key_type:
|
||||||
description:
|
description:
|
||||||
- The CSR's public key's type.
|
- The CSR's public key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with V(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.7.0
|
version_added: 1.7.0
|
||||||
@@ -214,57 +215,57 @@ public_key_data:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
|
returned: When RV(public_key_type=RSA) or RV(public_key_type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(public_key_type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(public_key_type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(public_key_type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(public_key_type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(public_key_type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(public_key_type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(public_key_type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(public_key_type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(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.
|
- For RV(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
|
type: int
|
||||||
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
|
returned: When RV(public_key_type=DSA) or RV(public_key_type=ECC)
|
||||||
public_key_fingerprints:
|
public_key_fingerprints:
|
||||||
description:
|
description:
|
||||||
- Fingerprints of CSR's public key.
|
- Fingerprints of CSR's public key.
|
||||||
@@ -276,24 +277,24 @@ public_key_fingerprints:
|
|||||||
subject_key_identifier:
|
subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The CSR's subject key identifier.
|
- The CSR's subject key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
- Is V(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_key_identifier:
|
authority_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The CSR's authority key identifier.
|
- The CSR's authority key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_cert_issuer:
|
authority_cert_issuer:
|
||||||
description:
|
description:
|
||||||
- The CSR's authority cert issuer as a list of general names.
|
- The CSR's authority cert issuer as a list of general names.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -301,7 +302,9 @@ authority_cert_issuer:
|
|||||||
authority_cert_serial_number:
|
authority_cert_serial_number:
|
||||||
description:
|
description:
|
||||||
- The CSR's authority cert serial number.
|
- The CSR's authority cert serial number.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 12345
|
sample: 12345
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ extends_documentation_fragment:
|
|||||||
attributes:
|
attributes:
|
||||||
check_mode:
|
check_mode:
|
||||||
support: full
|
support: full
|
||||||
|
details:
|
||||||
|
- Currently in check mode, private keys will not be (re-)generated, only the changed status is
|
||||||
|
set. This will change in community.crypto 3.0.0.
|
||||||
|
- From community.crypto 3.0.0 on, the module will ignore check mode and always behave as if
|
||||||
|
check mode is not active. If you think this breaks your use-case of this module, please
|
||||||
|
create an issue in the community.crypto repository.
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: full
|
support: full
|
||||||
options:
|
options:
|
||||||
@@ -34,6 +40,14 @@ options:
|
|||||||
description:
|
description:
|
||||||
- The existing CSR.
|
- The existing CSR.
|
||||||
type: str
|
type: str
|
||||||
|
privatekey_path:
|
||||||
|
description:
|
||||||
|
- The path to the private key to use when signing the certificate signing request.
|
||||||
|
- Either O(privatekey_path) or O(privatekey_content) must be specified, but not both.
|
||||||
|
privatekey_content:
|
||||||
|
description:
|
||||||
|
- The content of the private key to use when signing the certificate signing request.
|
||||||
|
- Either O(privatekey_path) or O(privatekey_content) must be specified, but not both.
|
||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.openssl_csr
|
- module: community.crypto.openssl_csr
|
||||||
'''
|
'''
|
||||||
@@ -44,12 +58,13 @@ EXAMPLES = r'''
|
|||||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||||
common_name: www.ansible.com
|
common_name: www.ansible.com
|
||||||
register: result
|
register: result
|
||||||
- debug:
|
- name: Print CSR
|
||||||
|
ansible.builtin.debug:
|
||||||
var: result.csr
|
var: result.csr
|
||||||
|
|
||||||
- name: Generate an OpenSSL Certificate Signing Request with an inline CSR
|
- name: Generate an OpenSSL Certificate Signing Request with an inline CSR
|
||||||
community.crypto.openssl_csr:
|
community.crypto.openssl_csr:
|
||||||
content: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.csr') }}"
|
content: "{{ lookup('ansible.builtin.file', '/etc/ssl/csr/www.ansible.com.csr') }}"
|
||||||
privatekey_content: "{{ private_key_content }}"
|
privatekey_content: "{{ private_key_content }}"
|
||||||
common_name: www.ansible.com
|
common_name: www.ansible.com
|
||||||
register: result
|
register: result
|
||||||
@@ -64,7 +79,7 @@ RETURN = r'''
|
|||||||
privatekey:
|
privatekey:
|
||||||
description:
|
description:
|
||||||
- Path to the TLS/SSL private key the CSR was generated for
|
- Path to the TLS/SSL private key the CSR was generated for
|
||||||
- Will be C(none) if the private key has been provided in I(privatekey_content).
|
- Will be V(none) if the private key has been provided in O(privatekey_content).
|
||||||
returned: changed or success
|
returned: changed or success
|
||||||
type: str
|
type: str
|
||||||
sample: /etc/ssl/private/ansible.com.pem
|
sample: /etc/ssl/private/ansible.com.pem
|
||||||
@@ -137,6 +152,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
|
|||||||
class CertificateSigningRequestModule(object):
|
class CertificateSigningRequestModule(object):
|
||||||
def __init__(self, module, module_backend):
|
def __init__(self, module, module_backend):
|
||||||
self.check_mode = module.check_mode
|
self.check_mode = module.check_mode
|
||||||
|
self.module = module
|
||||||
self.module_backend = module_backend
|
self.module_backend = module_backend
|
||||||
self.changed = False
|
self.changed = False
|
||||||
if module.params['content'] is not None:
|
if module.params['content'] is not None:
|
||||||
@@ -147,6 +163,16 @@ class CertificateSigningRequestModule(object):
|
|||||||
if self.module_backend.needs_regeneration():
|
if self.module_backend.needs_regeneration():
|
||||||
if not self.check_mode:
|
if not self.check_mode:
|
||||||
self.module_backend.generate_csr()
|
self.module_backend.generate_csr()
|
||||||
|
else:
|
||||||
|
self.module.deprecate(
|
||||||
|
'Check mode support for openssl_csr_pipe will change in community.crypto 3.0.0'
|
||||||
|
' to behave the same as without check mode. You can get that behavior right now'
|
||||||
|
' by adding `check_mode: false` to the openssl_csr_pipe task. If you think this'
|
||||||
|
' breaks your use-case of this module, please create an issue in the'
|
||||||
|
' community.crypto repository',
|
||||||
|
version='3.0.0',
|
||||||
|
collection_name='community.crypto',
|
||||||
|
)
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ description:
|
|||||||
- This module uses file common arguments to specify generated file permissions.
|
- This module uses file common arguments to specify generated file permissions.
|
||||||
- "Please note that the module regenerates existing DH params if they do not
|
- "Please note that the module regenerates existing DH params if they do not
|
||||||
match the module's options. If you are concerned that this could overwrite
|
match the module's options. If you are concerned that this could overwrite
|
||||||
your existing DH params, consider using the I(backup) option."
|
your existing DH params, consider using the O(backup) option."
|
||||||
- The module can use the cryptography Python library, or the C(openssl) executable.
|
- The module can use the cryptography Python library, or the C(openssl) executable.
|
||||||
By default, it tries to detect which one is available. This can be overridden
|
By default, it tries to detect which one is available. This can be overridden
|
||||||
with the I(select_crypto_backend) option.
|
with the O(select_crypto_backend) option.
|
||||||
requirements:
|
requirements:
|
||||||
- Either cryptography >= 2.0
|
- Either cryptography >= 2.0
|
||||||
- Or OpenSSL binary C(openssl)
|
- Or OpenSSL binary C(openssl)
|
||||||
@@ -70,16 +70,16 @@ options:
|
|||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- 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(openssl).
|
- The default choice is V(auto), which tries to use C(cryptography) if available, and falls back to C(openssl).
|
||||||
- If set to C(openssl), will try to use the OpenSSL C(openssl) executable.
|
- If set to V(openssl), will try to use the OpenSSL C(openssl) executable.
|
||||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography, openssl ]
|
choices: [ auto, cryptography, openssl ]
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
return_content:
|
return_content:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), will return the (current or generated) DH parameter's content as I(dhparams).
|
- If set to V(true), will return the (current or generated) DH parameter's content as RV(dhparams).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
@@ -120,12 +120,12 @@ filename:
|
|||||||
sample: /etc/ssl/dhparams.pem
|
sample: /etc/ssl/dhparams.pem
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created.
|
description: Name of backup file created.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/dhparams.pem.2019-03-09@11:22~
|
sample: /path/to/dhparams.pem.2019-03-09@11:22~
|
||||||
dhparams:
|
dhparams:
|
||||||
description: The (current or generated) DH params' content.
|
description: The (current or generated) DH params' content.
|
||||||
returned: if I(state) is C(present) and I(return_content) is C(true)
|
returned: if O(state) is V(present) and O(return_content) is V(true)
|
||||||
type: str
|
type: str
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
'''
|
'''
|
||||||
@@ -193,7 +193,7 @@ class DHParameterBase(object):
|
|||||||
"""Generate DH params."""
|
"""Generate DH params."""
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
# ony generate when necessary
|
# only generate when necessary
|
||||||
if self.force or not self._check_params_valid(module):
|
if self.force or not self._check_params_valid(module):
|
||||||
self._do_generate(module)
|
self._do_generate(module)
|
||||||
changed = True
|
changed = True
|
||||||
@@ -341,7 +341,7 @@ class DHParameterCryptography(DHParameterBase):
|
|||||||
try:
|
try:
|
||||||
with open(self.path, 'rb') as f:
|
with open(self.path, 'rb') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
params = self.crypto_backend.load_pem_parameters(data)
|
params = cryptography.hazmat.primitives.serialization.load_pem_parameters(data, backend=self.crypto_backend)
|
||||||
except Exception as dummy:
|
except Exception as dummy:
|
||||||
return False
|
return False
|
||||||
# Check parameters
|
# Check parameters
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ description:
|
|||||||
- This module allows one to (re-)generate PKCS#12.
|
- This module allows one to (re-)generate PKCS#12.
|
||||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||||
library. By default, it tries to detect which one is available, assuming none of the
|
library. By default, it tries to detect which one is available, assuming none of the
|
||||||
I(iter_size) and I(maciter_size) options are used. This can be overridden with the
|
O(iter_size) and O(maciter_size) options are used. This can be overridden with the
|
||||||
I(select_crypto_backend) option.
|
O(select_crypto_backend) option.
|
||||||
# Please note that the C(pyopenssl) backend has been deprecated in community.crypto x.y.0,
|
# Please note that the C(pyopenssl) backend has been deprecated in community.crypto x.y.0,
|
||||||
# and will be removed in community.crypto (x+1).0.0.
|
# and will be removed in community.crypto (x+1).0.0.
|
||||||
requirements:
|
requirements:
|
||||||
- PyOpenSSL >= 0.15 or cryptography >= 3.0
|
- PyOpenSSL >= 0.15, < 23.3.0 or cryptography >= 3.0
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- ansible.builtin.files
|
- ansible.builtin.files
|
||||||
- community.crypto.attributes
|
- community.crypto.attributes
|
||||||
@@ -39,21 +39,21 @@ attributes:
|
|||||||
options:
|
options:
|
||||||
action:
|
action:
|
||||||
description:
|
description:
|
||||||
- C(export) or C(parse) a PKCS#12.
|
- V(export) or V(parse) a PKCS#12.
|
||||||
type: str
|
type: str
|
||||||
default: export
|
default: export
|
||||||
choices: [ export, parse ]
|
choices: [ export, parse ]
|
||||||
other_certificates:
|
other_certificates:
|
||||||
description:
|
description:
|
||||||
- List of other certificates to include. Pre Ansible 2.8 this parameter was called I(ca_certificates).
|
- List of other certificates to include. Pre Ansible 2.8 this parameter was called O(ca_certificates).
|
||||||
- Assumes there is one PEM-encoded certificate per file. If a file contains multiple PEM certificates,
|
- Assumes there is one PEM-encoded certificate per file. If a file contains multiple PEM certificates,
|
||||||
set I(other_certificates_parse_all) to C(true).
|
set O(other_certificates_parse_all) to V(true).
|
||||||
type: list
|
type: list
|
||||||
elements: path
|
elements: path
|
||||||
aliases: [ ca_certificates ]
|
aliases: [ ca_certificates ]
|
||||||
other_certificates_parse_all:
|
other_certificates_parse_all:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), assumes that the files mentioned in I(other_certificates) can contain more than one
|
- If set to V(true), assumes that the files mentioned in O(other_certificates) can contain more than one
|
||||||
certificate per file (or even none per file).
|
certificate per file (or even none per file).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
@@ -77,21 +77,21 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Number of times to repeat the encryption step.
|
- Number of times to repeat the encryption step.
|
||||||
- This is B(not considered during idempotency checks).
|
- This is B(not considered during idempotency checks).
|
||||||
- This is only used by the C(pyopenssl) backend, or when I(encryption_level=compatibility2022).
|
- This is only used by the C(pyopenssl) backend, or when O(encryption_level=compatibility2022).
|
||||||
- When using it, the default is C(2048) for C(pyopenssl) and C(50000) for C(cryptography).
|
- When using it, the default is V(2048) for C(pyopenssl) and V(50000) for C(cryptography).
|
||||||
type: int
|
type: int
|
||||||
maciter_size:
|
maciter_size:
|
||||||
description:
|
description:
|
||||||
- Number of times to repeat the MAC step.
|
- Number of times to repeat the MAC step.
|
||||||
- This is B(not considered during idempotency checks).
|
- This is B(not considered during idempotency checks).
|
||||||
- This is only used by the C(pyopenssl) backend. When using it, the default is C(1).
|
- This is only used by the C(pyopenssl) backend. When using it, the default is V(1).
|
||||||
type: int
|
type: int
|
||||||
encryption_level:
|
encryption_level:
|
||||||
description:
|
description:
|
||||||
- Determines the encryption level used.
|
- Determines the encryption level used.
|
||||||
- C(auto) uses the default of the selected backend. For C(cryptography), this is what the
|
- V(auto) uses the default of the selected backend. For C(cryptography), this is what the
|
||||||
cryptography library's specific version considers the best available encryption.
|
cryptography library's specific version considers the best available encryption.
|
||||||
- C(compatibility2022) uses compatibility settings for older software in 2022.
|
- V(compatibility2022) uses compatibility settings for older software in 2022.
|
||||||
This is only supported by the C(cryptography) backend if cryptography >= 38.0.0 is available.
|
This is only supported by the C(cryptography) backend if cryptography >= 38.0.0 is available.
|
||||||
- B(Note) that this option is B(not used for idempotency).
|
- B(Note) that this option is B(not used for idempotency).
|
||||||
choices:
|
choices:
|
||||||
@@ -119,18 +119,18 @@ options:
|
|||||||
privatekey_path:
|
privatekey_path:
|
||||||
description:
|
description:
|
||||||
- File to read private key from.
|
- File to read private key from.
|
||||||
- Mutually exclusive with I(privatekey_content).
|
- Mutually exclusive with O(privatekey_content).
|
||||||
type: path
|
type: path
|
||||||
privatekey_content:
|
privatekey_content:
|
||||||
description:
|
description:
|
||||||
- Content of the private key file.
|
- Content of the private key file.
|
||||||
- Mutually exclusive with I(privatekey_path).
|
- Mutually exclusive with O(privatekey_path).
|
||||||
type: str
|
type: str
|
||||||
version_added: "2.3.0"
|
version_added: "2.3.0"
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Whether the file should exist or not.
|
- Whether the file should exist or not.
|
||||||
All parameters except C(path) are ignored when state is C(absent).
|
All parameters except O(path) are ignored when state is V(absent).
|
||||||
choices: [ absent, present ]
|
choices: [ absent, present ]
|
||||||
default: present
|
default: present
|
||||||
type: str
|
type: str
|
||||||
@@ -146,18 +146,18 @@ options:
|
|||||||
default: false
|
default: false
|
||||||
return_content:
|
return_content:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), will return the (current or generated) PKCS#12's content as I(pkcs12).
|
- If set to V(true), will return the (current or generated) PKCS#12's content as RV(pkcs12).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- 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).
|
- The default choice is V(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||||
If I(iter_size) is used together with I(encryption_level != compatibility2022), or if I(maciter_size) is used,
|
If O(iter_size) is used together with O(encryption_level) is not V(compatibility2022), or if O(maciter_size) is used,
|
||||||
C(auto) will always result in C(pyopenssl) to be chosen for backwards compatibility.
|
V(auto) will always result in C(pyopenssl) to be chosen for backwards compatibility.
|
||||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
- If set to V(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
# - Please note that the C(pyopenssl) backend has been deprecated in community.crypto x.y.0, and will be
|
# - Please note that the C(pyopenssl) backend has been deprecated in community.crypto x.y.0, and will be
|
||||||
# removed in community.crypto (x+1).0.0.
|
# removed in community.crypto (x+1).0.0.
|
||||||
# From that point on, only the C(cryptography) backend will be available.
|
# From that point on, only the C(cryptography) backend will be available.
|
||||||
@@ -255,12 +255,12 @@ privatekey:
|
|||||||
sample: /etc/ssl/private/ansible.com.pem
|
sample: /etc/ssl/private/ansible.com.pem
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created.
|
description: Name of backup file created.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/ansible.com.pem.2019-03-09@11:22~
|
sample: /path/to/ansible.com.pem.2019-03-09@11:22~
|
||||||
pkcs12:
|
pkcs12:
|
||||||
description: The (current or generated) PKCS#12's content Base64 encoded.
|
description: The (current or generated) PKCS#12's content Base64 encoded.
|
||||||
returned: if I(state) is C(present) and I(return_content) is C(true)
|
returned: if O(state) is V(present) and O(return_content) is V(true)
|
||||||
type: str
|
type: str
|
||||||
version_added: "1.0.0"
|
version_added: "1.0.0"
|
||||||
'''
|
'''
|
||||||
@@ -302,11 +302,13 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
|
|||||||
|
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '3.0'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '3.0'
|
||||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||||
|
MAXIMAL_PYOPENSSL_VERSION = '23.3.0'
|
||||||
|
|
||||||
PYOPENSSL_IMP_ERR = None
|
PYOPENSSL_IMP_ERR = None
|
||||||
try:
|
try:
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
|
from OpenSSL.crypto import load_pkcs12 as _load_pkcs12 # this got removed in pyOpenSSL 23.3.0
|
||||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||||
except (ImportError, AttributeError):
|
except (ImportError, AttributeError):
|
||||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||||
@@ -711,7 +713,11 @@ def select_backend(module, backend):
|
|||||||
if backend == 'auto':
|
if backend == 'auto':
|
||||||
# Detection what is possible
|
# Detection what is possible
|
||||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
can_use_pyopenssl = (
|
||||||
|
PYOPENSSL_FOUND and
|
||||||
|
PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) and
|
||||||
|
PYOPENSSL_VERSION < LooseVersion(MAXIMAL_PYOPENSSL_VERSION)
|
||||||
|
)
|
||||||
|
|
||||||
# If no restrictions are provided, first try cryptography, then pyOpenSSL
|
# If no restrictions are provided, first try cryptography, then pyOpenSSL
|
||||||
if (
|
if (
|
||||||
@@ -728,14 +734,17 @@ def select_backend(module, backend):
|
|||||||
# Success?
|
# Success?
|
||||||
if backend == 'auto':
|
if backend == 'auto':
|
||||||
module.fail_json(msg=("Cannot detect any of the required Python libraries "
|
module.fail_json(msg=("Cannot detect any of the required Python libraries "
|
||||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
"cryptography (>= {0}) or PyOpenSSL (>= {1}, < {2})").format(
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||||
MINIMAL_PYOPENSSL_VERSION))
|
MINIMAL_PYOPENSSL_VERSION,
|
||||||
|
MAXIMAL_PYOPENSSL_VERSION))
|
||||||
|
|
||||||
if backend == 'pyopenssl':
|
if backend == 'pyopenssl':
|
||||||
if not PYOPENSSL_FOUND:
|
if not PYOPENSSL_FOUND:
|
||||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
msg = missing_required_lib(
|
||||||
exception=PYOPENSSL_IMP_ERR)
|
'pyOpenSSL >= {0}, < {1}'.format(MINIMAL_PYOPENSSL_VERSION, MAXIMAL_PYOPENSSL_VERSION)
|
||||||
|
)
|
||||||
|
module.fail_json(msg=msg, exception=PYOPENSSL_IMP_ERR)
|
||||||
# module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
# module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||||
# version='x.0.0', collection_name='community.crypto')
|
# version='x.0.0', collection_name='community.crypto')
|
||||||
return backend, PkcsPyOpenSSL(module)
|
return backend, PkcsPyOpenSSL(module)
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ module: openssl_privatekey
|
|||||||
short_description: Generate OpenSSL private keys
|
short_description: Generate OpenSSL private keys
|
||||||
description:
|
description:
|
||||||
- This module allows one to (re)generate OpenSSL private keys.
|
- This module allows one to (re)generate OpenSSL private keys.
|
||||||
- The default mode for the private key file will be C(0600) if I(mode) is not explicitly set.
|
- The default mode for the private key file will be V(0600) if O(mode) is not explicitly set.
|
||||||
|
- "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 O(backup) option."
|
||||||
author:
|
author:
|
||||||
- Yanis Guenane (@Spredzy)
|
- Yanis Guenane (@Spredzy)
|
||||||
- Felix Fontein (@felixfontein)
|
- Felix Fontein (@felixfontein)
|
||||||
@@ -45,8 +50,8 @@ options:
|
|||||||
default: false
|
default: false
|
||||||
path:
|
path:
|
||||||
description:
|
description:
|
||||||
- Name of the file in which the generated TLS/SSL private key will be written. It will have C(0600) mode
|
- Name of the file in which the generated TLS/SSL private key will be written. It will have V(0600) mode
|
||||||
if I(mode) is not explicitly set.
|
if O(mode) is not explicitly set.
|
||||||
type: path
|
type: path
|
||||||
required: true
|
required: true
|
||||||
format:
|
format:
|
||||||
@@ -61,10 +66,10 @@ options:
|
|||||||
default: false
|
default: false
|
||||||
return_content:
|
return_content:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), will return the (current or generated) private key's content as I(privatekey).
|
- If set to V(true), will return the (current or generated) private key's content as RV(privatekey).
|
||||||
- Note that especially if the private key is not encrypted, you have to make sure that the returned
|
- Note that especially if the private key is not encrypted, you have to make sure that the returned
|
||||||
value is treated appropriately and not accidentally written to logs etc.! Use with care!
|
value is treated appropriately and not accidentally written to logs etc.! Use with care!
|
||||||
- Use Ansible's I(no_log) task option to avoid the output being shown. See also
|
- Use Ansible's C(no_log) task option to avoid the output being shown. See also
|
||||||
U(https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-keep-secret-data-in-my-playbook).
|
U(https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-keep-secret-data-in-my-playbook).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
@@ -101,6 +106,12 @@ EXAMPLES = r'''
|
|||||||
community.crypto.openssl_privatekey:
|
community.crypto.openssl_privatekey:
|
||||||
path: /etc/ssl/private/ansible.com.pem
|
path: /etc/ssl/private/ansible.com.pem
|
||||||
type: DSA
|
type: DSA
|
||||||
|
|
||||||
|
- name: Generate an OpenSSL private key with elliptic curve cryptography (ECC)
|
||||||
|
community.crypto.openssl_privatekey:
|
||||||
|
path: /etc/ssl/private/ansible.com.pem
|
||||||
|
type: ECC
|
||||||
|
curve: secp256r1
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = r'''
|
RETURN = r'''
|
||||||
@@ -116,7 +127,7 @@ type:
|
|||||||
sample: RSA
|
sample: RSA
|
||||||
curve:
|
curve:
|
||||||
description: Elliptic curve used to generate the TLS/SSL private key.
|
description: Elliptic curve used to generate the TLS/SSL private key.
|
||||||
returned: changed or success, and I(type) is C(ECC)
|
returned: changed or success, and O(type) is V(ECC)
|
||||||
type: str
|
type: str
|
||||||
sample: secp256r1
|
sample: secp256r1
|
||||||
filename:
|
filename:
|
||||||
@@ -138,14 +149,14 @@ fingerprint:
|
|||||||
sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b"
|
sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b"
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created.
|
description: Name of backup file created.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/privatekey.pem.2019-03-09@11:22~
|
sample: /path/to/privatekey.pem.2019-03-09@11:22~
|
||||||
privatekey:
|
privatekey:
|
||||||
description:
|
description:
|
||||||
- The (current or generated) private key's content.
|
- The (current or generated) private key's content.
|
||||||
- Will be Base64-encoded if the key is in raw format.
|
- Will be Base64-encoded if the key is in raw format.
|
||||||
returned: if I(state) is C(present) and I(return_content) is C(true)
|
returned: if O(state) is V(present) and O(return_content) is V(true)
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ short_description: Convert OpenSSL private keys
|
|||||||
version_added: 2.1.0
|
version_added: 2.1.0
|
||||||
description:
|
description:
|
||||||
- This module allows one to convert OpenSSL private keys.
|
- This module allows one to convert OpenSSL private keys.
|
||||||
- The default mode for the private key file will be C(0600) if I(mode) is not explicitly set.
|
- The default mode for the private key file will be V(0600) if O(mode) is not explicitly set.
|
||||||
author:
|
author:
|
||||||
- Felix Fontein (@felixfontein)
|
- Felix Fontein (@felixfontein)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
@@ -34,8 +34,8 @@ attributes:
|
|||||||
options:
|
options:
|
||||||
dest_path:
|
dest_path:
|
||||||
description:
|
description:
|
||||||
- Name of the file in which the generated TLS/SSL private key will be written. It will have C(0600) mode
|
- Name of the file in which the generated TLS/SSL private key will be written. It will have V(0600) mode
|
||||||
if I(mode) is not explicitly set.
|
if O(mode) is not explicitly set.
|
||||||
type: path
|
type: path
|
||||||
required: true
|
required: true
|
||||||
backup:
|
backup:
|
||||||
@@ -59,7 +59,7 @@ EXAMPLES = r'''
|
|||||||
RETURN = r'''
|
RETURN = r'''
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created.
|
description: Name of backup file created.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/privatekey.pem.2019-03-09@11:22~
|
sample: /path/to/privatekey.pem.2019-03-09@11:22~
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ description:
|
|||||||
- This module allows one to query information on OpenSSL private keys.
|
- This module allows one to query information on OpenSSL private keys.
|
||||||
- In case the key consistency checks fail, the module will fail as this indicates a faked
|
- In case the key consistency checks fail, the module will fail as this indicates a faked
|
||||||
private key. In this case, all return variables are still returned. Note that key consistency
|
private key. In this case, all return variables are still returned. Note that key consistency
|
||||||
checks are not available all key types; if none is available, C(none) is returned for
|
checks are not available all key types; if none is available, V(none) is returned for
|
||||||
C(key_is_consistent).
|
RV(key_is_consistent).
|
||||||
- It uses the cryptography python library to interact with OpenSSL.
|
- It uses the cryptography python library to interact with OpenSSL.
|
||||||
requirements:
|
requirements:
|
||||||
- cryptography >= 1.2.3
|
- cryptography >= 1.2.3
|
||||||
@@ -37,7 +37,7 @@ options:
|
|||||||
content:
|
content:
|
||||||
description:
|
description:
|
||||||
- Content of the private key file.
|
- Content of the private key file.
|
||||||
- Either I(path) or I(content) must be specified, but not both.
|
- Either O(path) or O(content) must be specified, but not both.
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
passphrase:
|
passphrase:
|
||||||
@@ -47,7 +47,7 @@ options:
|
|||||||
return_private_key_data:
|
return_private_key_data:
|
||||||
description:
|
description:
|
||||||
- Whether to return private key data.
|
- Whether to return private key data.
|
||||||
- Only set this to C(true) when you want private information about this key to
|
- Only set this to V(true) when you want private information about this key to
|
||||||
leave the remote machine.
|
leave the remote machine.
|
||||||
- "B(WARNING:) you have to make sure that private key data is not accidentally logged!"
|
- "B(WARNING:) you have to make sure that private key data is not accidentally logged!"
|
||||||
type: bool
|
type: bool
|
||||||
@@ -60,6 +60,9 @@ options:
|
|||||||
avoid private key material to be transported around and computed with, and only do
|
avoid private key material to be transported around and computed with, and only do
|
||||||
so when requested explicitly. This can potentially prevent
|
so when requested explicitly. This can potentially prevent
|
||||||
L(side-channel attacks,https://en.wikipedia.org/wiki/Side-channel_attack).
|
L(side-channel attacks,https://en.wikipedia.org/wiki/Side-channel_attack).
|
||||||
|
- Note that consistency checks only work for certain key types, and might depend on the
|
||||||
|
version of the cryptography library. For example, with cryptography 42.0.0 and newer
|
||||||
|
consistency of RSA keys can no longer be checked.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
version_added: 2.0.0
|
version_added: 2.0.0
|
||||||
@@ -67,8 +70,8 @@ options:
|
|||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -76,9 +79,8 @@ options:
|
|||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.openssl_privatekey
|
- module: community.crypto.openssl_privatekey
|
||||||
- module: community.crypto.openssl_privatekey_pipe
|
- module: community.crypto.openssl_privatekey_pipe
|
||||||
- ref: community.crypto.openssl_privatekey_info filter <ansible_collections.community.crypto.openssl_privatekey_info_filter>
|
- plugin: community.crypto.openssl_privatekey_info
|
||||||
# - plugin: community.crypto.openssl_privatekey_info
|
plugin_type: filter
|
||||||
# plugin_type: filter
|
|
||||||
description: A filter variant of this module.
|
description: A filter variant of this module.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -108,10 +110,10 @@ can_parse_key:
|
|||||||
type: bool
|
type: bool
|
||||||
key_is_consistent:
|
key_is_consistent:
|
||||||
description:
|
description:
|
||||||
- Whether the key is consistent. Can also return C(none) next to C(true) and
|
- Whether the key is consistent. Can also return V(none) next to V(true) and
|
||||||
C(false), to indicate that consistency could not be checked.
|
V(false), to indicate that consistency could not be checked.
|
||||||
- In case the check returns C(false), the module will fail.
|
- In case the check returns V(false), the module will fail.
|
||||||
returned: when I(check_consistency=true)
|
returned: when O(check_consistency=true)
|
||||||
type: bool
|
type: bool
|
||||||
public_key:
|
public_key:
|
||||||
description: Private key's public key in PEM format.
|
description: Private key's public key in PEM format.
|
||||||
@@ -129,8 +131,8 @@ public_key_fingerprints:
|
|||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- The key's type.
|
- The key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with V(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: RSA
|
sample: RSA
|
||||||
@@ -144,61 +146,61 @@ public_data:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA) or C(type=DSA)
|
returned: When RV(type=RSA) or RV(type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(type=ECC)
|
returned: When RV(type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(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.
|
- For RV(type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA) or C(type=ECC)
|
returned: When RV(type=DSA) or RV(type=ECC)
|
||||||
private_data:
|
private_data:
|
||||||
description:
|
description:
|
||||||
- Private key data. Depends on key type.
|
- Private key data. Depends on key type.
|
||||||
returned: success and when I(return_private_key_data) is set to C(true)
|
returned: success and when O(return_private_key_data) is set to V(true)
|
||||||
type: dict
|
type: dict
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ version_added: 1.3.0
|
|||||||
description:
|
description:
|
||||||
- This module allows one to (re)generate OpenSSL private keys without disk access.
|
- This module allows one to (re)generate OpenSSL private keys without disk access.
|
||||||
- This allows to read and write keys to vaults without having to write intermediate versions to disk.
|
- This allows to read and write keys to vaults without having to write intermediate versions to disk.
|
||||||
- Make sure to not write the result of this module into logs or to the console, as it contains private key data! Use the I(no_log) task option to be sure.
|
- Make sure to not write the result of this module into logs or to the console, as it contains private key data! Use the C(no_log) task option to be sure.
|
||||||
- Note that this module is implemented as an L(action plugin,https://docs.ansible.com/ansible/latest/plugins/action.html)
|
- Note that this module is implemented as an L(action plugin,https://docs.ansible.com/ansible/latest/plugins/action.html)
|
||||||
and will always be executed on the controller.
|
and will always be executed on the controller.
|
||||||
author:
|
author:
|
||||||
@@ -36,6 +36,12 @@ attributes:
|
|||||||
- This action runs completely on the controller.
|
- This action runs completely on the controller.
|
||||||
check_mode:
|
check_mode:
|
||||||
support: full
|
support: full
|
||||||
|
details:
|
||||||
|
- Currently in check mode, private keys will not be (re-)generated, only the changed status is
|
||||||
|
set. This will change in community.crypto 3.0.0.
|
||||||
|
- From community.crypto 3.0.0 on, the module will ignore check mode and always behave as if
|
||||||
|
check mode is not active. If you think this breaks your use-case of this module, please
|
||||||
|
create an issue in the community.crypto repository.
|
||||||
diff_mode:
|
diff_mode:
|
||||||
support: full
|
support: full
|
||||||
options:
|
options:
|
||||||
@@ -47,16 +53,39 @@ options:
|
|||||||
type: str
|
type: str
|
||||||
content_base64:
|
content_base64:
|
||||||
description:
|
description:
|
||||||
- Set to C(true) if the content is base64 encoded.
|
- Set to V(true) if the content is base64 encoded.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
return_current_key:
|
return_current_key:
|
||||||
description:
|
description:
|
||||||
- Set to C(true) to return the current private key when the module did not generate a new one.
|
- Set to V(true) to return the current private key when the module did not generate a new one.
|
||||||
- Note that in case of check mode, when this option is not set to C(true), the module always returns the
|
- Note that in case of check mode, when this option is not set to V(true), the module always returns the
|
||||||
current key (if it was provided) and Ansible will replace it by C(VALUE_SPECIFIED_IN_NO_LOG_PARAMETER).
|
current key (if it was provided) and Ansible will replace it by C(VALUE_SPECIFIED_IN_NO_LOG_PARAMETER).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
|
regenerate:
|
||||||
|
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 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 V(full_idempotence)
|
||||||
|
is specified.
|
||||||
|
- If set to V(never), the module will fail if the key cannot be read or the passphrase
|
||||||
|
is not matching, and will never regenerate an existing key.
|
||||||
|
- If set to V(fail), the module will fail if the key does not correspond to the module's
|
||||||
|
options.
|
||||||
|
- If set to V(partial_idempotence), the key will be regenerated if it does not conform to
|
||||||
|
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
|
||||||
|
the key is protected by an unknown passphrase, or when they key is not protected by a
|
||||||
|
passphrase, but a passphrase is specified.
|
||||||
|
- If set to V(full_idempotence), the key will be regenerated if it does not conform to the
|
||||||
|
module's options. This is also the case if the key cannot be read (broken file), the key
|
||||||
|
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 V(always), the module will always regenerate the key.
|
||||||
|
- Note that if O(format_mismatch) is set to V(convert) and everything matches except the
|
||||||
|
format, the key will always be converted, except if O(regenerate) is set to V(always).
|
||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.openssl_privatekey
|
- module: community.crypto.openssl_privatekey
|
||||||
- module: community.crypto.openssl_privatekey_info
|
- module: community.crypto.openssl_privatekey_info
|
||||||
@@ -65,15 +94,16 @@ seealso:
|
|||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA)
|
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA)
|
||||||
community.crypto.openssl_privatekey_pipe:
|
community.crypto.openssl_privatekey_pipe:
|
||||||
path: /etc/ssl/private/ansible.com.pem
|
|
||||||
register: output
|
register: output
|
||||||
no_log: true # make sure that private key data is not accidentally revealed in logs!
|
no_log: true # make sure that private key data is not accidentally revealed in logs!
|
||||||
- name: Show generated key
|
- name: Show generated key
|
||||||
debug:
|
ansible.builtin.debug:
|
||||||
msg: "{{ output.privatekey }}"
|
msg: "{{ output.privatekey }}"
|
||||||
# DO NOT OUTPUT KEY MATERIAL TO CONSOLE OR LOGS IN PRODUCTION!
|
# DO NOT OUTPUT KEY MATERIAL TO CONSOLE OR LOGS IN PRODUCTION!
|
||||||
|
|
||||||
- block:
|
|
||||||
|
- name: Generate or update a Mozilla sops encrypted key
|
||||||
|
block:
|
||||||
- name: Update sops-encrypted key with the community.sops collection
|
- name: Update sops-encrypted key with the community.sops collection
|
||||||
community.crypto.openssl_privatekey_pipe:
|
community.crypto.openssl_privatekey_pipe:
|
||||||
content: "{{ lookup('community.sops.sops', 'private_key.pem.sops') }}"
|
content: "{{ lookup('community.sops.sops', 'private_key.pem.sops') }}"
|
||||||
@@ -88,7 +118,7 @@ EXAMPLES = r'''
|
|||||||
when: output is changed
|
when: output is changed
|
||||||
always:
|
always:
|
||||||
- name: Make sure that output (which contains the private key) is overwritten
|
- name: Make sure that output (which contains the private key) is overwritten
|
||||||
set_fact:
|
ansible.builtin.set_fact:
|
||||||
output: ''
|
output: ''
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -105,7 +135,7 @@ type:
|
|||||||
sample: RSA
|
sample: RSA
|
||||||
curve:
|
curve:
|
||||||
description: Elliptic curve used to generate the TLS/SSL private key.
|
description: Elliptic curve used to generate the TLS/SSL private key.
|
||||||
returned: changed or success, and I(type) is C(ECC)
|
returned: changed or success, and O(type) is V(ECC)
|
||||||
type: str
|
type: str
|
||||||
sample: secp256r1
|
sample: secp256r1
|
||||||
fingerprint:
|
fingerprint:
|
||||||
@@ -124,8 +154,8 @@ privatekey:
|
|||||||
description:
|
description:
|
||||||
- The generated private key's content.
|
- The generated private key's content.
|
||||||
- Please note that if the result is not changed, the current private key will only be returned
|
- Please note that if the result is not changed, the current private key will only be returned
|
||||||
if the I(return_current_key) option is set to C(true).
|
if the O(return_current_key) option is set to V(true).
|
||||||
- Will be Base64-encoded if the key is in raw format.
|
- Will be Base64-encoded if the key is in raw format.
|
||||||
returned: changed, or I(return_current_key) is C(true)
|
returned: changed, or O(return_current_key) is V(true)
|
||||||
type: str
|
type: str
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ short_description: Generate an OpenSSL public key from its private key.
|
|||||||
description:
|
description:
|
||||||
- This module allows one to (re)generate public keys from their private keys.
|
- This module allows one to (re)generate public keys from their private keys.
|
||||||
- Public keys are generated in PEM or OpenSSH format. Private keys must be OpenSSL PEM keys.
|
- Public keys are generated in PEM or OpenSSH format. Private keys must be OpenSSL PEM keys.
|
||||||
OpenSSH private keys are not supported, use the M(community.crypto.openssh_keypair) module to manage these.
|
B(OpenSSH private keys are not supported), use the M(community.crypto.openssh_keypair) module to manage these.
|
||||||
- The module uses the cryptography Python library.
|
- The module uses the cryptography Python library.
|
||||||
requirements:
|
requirements:
|
||||||
- cryptography >= 1.2.3 (older versions might work as well)
|
- cryptography >= 1.2.3 (older versions might work as well)
|
||||||
- Needs cryptography >= 1.4 if I(format) is C(OpenSSH)
|
- Needs cryptography >= 1.4 if O(format) is C(OpenSSH)
|
||||||
author:
|
author:
|
||||||
- Yanis Guenane (@Spredzy)
|
- Yanis Guenane (@Spredzy)
|
||||||
- Felix Fontein (@felixfontein)
|
- Felix Fontein (@felixfontein)
|
||||||
@@ -61,14 +61,14 @@ options:
|
|||||||
privatekey_path:
|
privatekey_path:
|
||||||
description:
|
description:
|
||||||
- Path to the TLS/SSL private key from which to generate the public key.
|
- Path to the TLS/SSL private key from which to generate the public key.
|
||||||
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
|
- Either O(privatekey_path) or O(privatekey_content) must be specified, but not both.
|
||||||
If I(state) is C(present), one of them is required.
|
If O(state) is V(present), one of them is required.
|
||||||
type: path
|
type: path
|
||||||
privatekey_content:
|
privatekey_content:
|
||||||
description:
|
description:
|
||||||
- The content of the TLS/SSL private key from which to generate the public key.
|
- The content of the TLS/SSL private key from which to generate the public key.
|
||||||
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
|
- Either O(privatekey_path) or O(privatekey_content) must be specified, but not both.
|
||||||
If I(state) is C(present), one of them is required.
|
If O(state) is V(present), one of them is required.
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
privatekey_passphrase:
|
privatekey_passphrase:
|
||||||
@@ -84,14 +84,14 @@ options:
|
|||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
return_content:
|
return_content:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), will return the (current or generated) public key's content as I(publickey).
|
- If set to V(true), will return the (current or generated) public key's content as RV(publickey).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
@@ -145,7 +145,7 @@ RETURN = r'''
|
|||||||
privatekey:
|
privatekey:
|
||||||
description:
|
description:
|
||||||
- Path to the TLS/SSL private key the public key was generated from.
|
- Path to the TLS/SSL private key the public key was generated from.
|
||||||
- Will be C(none) if the private key has been provided in I(privatekey_content).
|
- Will be V(none) if the private key has been provided in O(privatekey_content).
|
||||||
returned: changed or success
|
returned: changed or success
|
||||||
type: str
|
type: str
|
||||||
sample: /etc/ssl/private/ansible.com.pem
|
sample: /etc/ssl/private/ansible.com.pem
|
||||||
@@ -173,12 +173,12 @@ fingerprint:
|
|||||||
sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b"
|
sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b"
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created.
|
description: Name of backup file created.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/publickey.pem.2019-03-09@11:22~
|
sample: /path/to/publickey.pem.2019-03-09@11:22~
|
||||||
publickey:
|
publickey:
|
||||||
description: The (current or generated) public key's content.
|
description: The (current or generated) public key's content.
|
||||||
returned: if I(state) is C(present) and I(return_content) is C(true)
|
returned: if O(state) is V(present) and O(return_content) is V(true)
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ options:
|
|||||||
content:
|
content:
|
||||||
description:
|
description:
|
||||||
- Content of the public key file.
|
- Content of the public key file.
|
||||||
- Either I(path) or I(content) must be specified, but not both.
|
- Either O(path) or O(content) must be specified, but not both.
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -47,9 +47,8 @@ options:
|
|||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.openssl_publickey
|
- module: community.crypto.openssl_publickey
|
||||||
- module: community.crypto.openssl_privatekey_info
|
- module: community.crypto.openssl_privatekey_info
|
||||||
- ref: community.crypto.openssl_publickey_info filter <ansible_collections.community.crypto.openssl_publickey_info_filter>
|
- plugin: community.crypto.openssl_publickey_info
|
||||||
# - plugin: community.crypto.openssl_publickey_info
|
plugin_type: filter
|
||||||
# plugin_type: filter
|
|
||||||
description: A filter variant of this module.
|
description: A filter variant of this module.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -85,8 +84,8 @@ fingerprints:
|
|||||||
type:
|
type:
|
||||||
description:
|
description:
|
||||||
- The key's type.
|
- The key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with V(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: RSA
|
sample: RSA
|
||||||
@@ -100,57 +99,57 @@ public_data:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA) or C(type=DSA)
|
returned: When RV(type=RSA) or RV(type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=RSA)
|
returned: When RV(type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA)
|
returned: When RV(type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(type=ECC)
|
returned: When RV(type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=ECC)
|
returned: When RV(type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(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.
|
- For RV(type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(type=DSA) or C(type=ECC)
|
returned: When RV(type=DSA) or RV(type=ECC)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ options:
|
|||||||
privatekey_path:
|
privatekey_path:
|
||||||
description:
|
description:
|
||||||
- The path to the private key to use when signing.
|
- The path to the private key to use when signing.
|
||||||
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
|
- Either O(privatekey_path) or O(privatekey_content) must be specified, but not both.
|
||||||
type: path
|
type: path
|
||||||
privatekey_content:
|
privatekey_content:
|
||||||
description:
|
description:
|
||||||
- The content of the private key to use when signing the certificate signing request.
|
- The content of the private key to use when signing the certificate signing request.
|
||||||
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
|
- Either O(privatekey_path) or O(privatekey_content) must be specified, but not both.
|
||||||
type: str
|
type: str
|
||||||
privatekey_passphrase:
|
privatekey_passphrase:
|
||||||
description:
|
description:
|
||||||
@@ -56,8 +56,8 @@ options:
|
|||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -87,7 +87,7 @@ EXAMPLES = r'''
|
|||||||
register: verify
|
register: verify
|
||||||
|
|
||||||
- name: Make sure the signature is valid
|
- name: Make sure the signature is valid
|
||||||
assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- verify.valid
|
- verify.valid
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ options:
|
|||||||
certificate_path:
|
certificate_path:
|
||||||
description:
|
description:
|
||||||
- The path to the certificate used to verify the signature.
|
- The path to the certificate used to verify the signature.
|
||||||
- Either I(certificate_path) or I(certificate_content) must be specified, but not both.
|
- Either O(certificate_path) or O(certificate_content) must be specified, but not both.
|
||||||
type: path
|
type: path
|
||||||
certificate_content:
|
certificate_content:
|
||||||
description:
|
description:
|
||||||
- The content of the certificate used to verify the signature.
|
- The content of the certificate used to verify the signature.
|
||||||
- Either I(certificate_path) or I(certificate_content) must be specified, but not both.
|
- Either O(certificate_path) or O(certificate_content) must be specified, but not both.
|
||||||
type: str
|
type: str
|
||||||
signature:
|
signature:
|
||||||
description: Base64 encoded signature.
|
description: Base64 encoded signature.
|
||||||
@@ -49,8 +49,8 @@ options:
|
|||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -80,14 +80,14 @@ EXAMPLES = r'''
|
|||||||
register: verify
|
register: verify
|
||||||
|
|
||||||
- name: Make sure the signature is valid
|
- name: Make sure the signature is valid
|
||||||
assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- verify.valid
|
- verify.valid
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = r'''
|
RETURN = r'''
|
||||||
valid:
|
valid:
|
||||||
description: C(true) means the signature was valid for the given file, C(false) means it was not.
|
description: V(true) means the signature was valid for the given file, V(false) means it was not.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ DOCUMENTATION = r'''
|
|||||||
module: x509_certificate
|
module: x509_certificate
|
||||||
short_description: Generate and/or check OpenSSL certificates
|
short_description: Generate and/or check OpenSSL certificates
|
||||||
description:
|
description:
|
||||||
- It implements a notion of provider (one of C(selfsigned), C(ownca), C(acme), and C(entrust))
|
- It implements a notion of provider (one of V(selfsigned), V(ownca), V(acme), and V(entrust))
|
||||||
for your certificate.
|
for your certificate.
|
||||||
- "Please note that the module regenerates existing certificate if it does not match the module's
|
- "Please note that the module regenerates existing certificate if it does not match the module's
|
||||||
options, or if it seems to be corrupt. If you are concerned that this could overwrite
|
options, or if it seems to be corrupt. If you are concerned that this could overwrite
|
||||||
your existing certificate, consider using the I(backup) option."
|
your existing certificate, consider using the O(backup) option."
|
||||||
- Note that this module was called C(openssl_certificate) when included directly in Ansible up to version 2.9.
|
- Note that this module was called C(openssl_certificate) when included directly in Ansible up to version 2.9.
|
||||||
When moved to the collection C(community.crypto), it was renamed to
|
When moved to the collection C(community.crypto), it was renamed to
|
||||||
M(community.crypto.x509_certificate). From Ansible 2.10 on, it can still be used by the
|
M(community.crypto.x509_certificate). From Ansible 2.10 on, it can still be used by the
|
||||||
old short name (or by C(ansible.builtin.openssl_certificate)), which redirects to
|
old short name (or by C(ansible.builtin.openssl_certificate)), which redirects to
|
||||||
C(community.crypto.x509_certificate). When using FQCNs or when using the
|
M(community.crypto.x509_certificate). When using FQCNs or when using the
|
||||||
L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook)
|
L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook)
|
||||||
keyword, the new name M(community.crypto.x509_certificate) should be used to avoid
|
keyword, the new name M(community.crypto.x509_certificate) should be used to avoid
|
||||||
a deprecation warning.
|
a deprecation warning.
|
||||||
@@ -67,15 +67,15 @@ options:
|
|||||||
Please see the examples on how to emulate it with
|
Please see the examples on how to emulate it with
|
||||||
M(community.crypto.x509_certificate_info), M(community.crypto.openssl_csr_info),
|
M(community.crypto.x509_certificate_info), M(community.crypto.openssl_csr_info),
|
||||||
M(community.crypto.openssl_privatekey_info) and M(ansible.builtin.assert).
|
M(community.crypto.openssl_privatekey_info) and M(ansible.builtin.assert).
|
||||||
- "The C(entrust) provider was added for Ansible 2.9 and requires credentials for the
|
- "The V(entrust) provider was added for Ansible 2.9 and requires credentials for the
|
||||||
L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API."
|
L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API."
|
||||||
- Required if I(state) is C(present).
|
- Required if O(state) is V(present).
|
||||||
type: str
|
type: str
|
||||||
choices: [ acme, entrust, ownca, selfsigned ]
|
choices: [ acme, entrust, ownca, selfsigned ]
|
||||||
|
|
||||||
return_content:
|
return_content:
|
||||||
description:
|
description:
|
||||||
- If set to C(true), will return the (current or generated) certificate's content as I(certificate).
|
- If set to V(true), will return the (current or generated) certificate's content as RV(certificate).
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
@@ -175,7 +175,8 @@ EXAMPLES = r'''
|
|||||||
path: /etc/ssl/csr/ansible.com.key
|
path: /etc/ssl/csr/ansible.com.key
|
||||||
register: result_privatekey
|
register: result_privatekey
|
||||||
|
|
||||||
- assert:
|
- name: Check conditions on certificate, CSR, and private key
|
||||||
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
# When private key was specified for assertonly, this was checked:
|
# When private key was specified for assertonly, this was checked:
|
||||||
- result.public_key == result_privatekey.public_key
|
- result.public_key == result_privatekey.public_key
|
||||||
@@ -221,12 +222,12 @@ filename:
|
|||||||
sample: /etc/ssl/crt/www.ansible.com.crt
|
sample: /etc/ssl/crt/www.ansible.com.crt
|
||||||
backup_file:
|
backup_file:
|
||||||
description: Name of backup file created.
|
description: Name of backup file created.
|
||||||
returned: changed and if I(backup) is C(true)
|
returned: changed and if O(backup) is V(true)
|
||||||
type: str
|
type: str
|
||||||
sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~
|
sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~
|
||||||
certificate:
|
certificate:
|
||||||
description: The (current or generated) certificate's content.
|
description: The (current or generated) certificate's content.
|
||||||
returned: if I(state) is C(present) and I(return_content) is C(true)
|
returned: if O(state) is V(present) and O(return_content) is V(true)
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
'''
|
'''
|
||||||
|
|||||||
280
plugins/modules/x509_certificate_convert.py
Normal file
280
plugins/modules/x509_certificate_convert.py
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024, 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 = r'''
|
||||||
|
---
|
||||||
|
module: x509_certificate_convert
|
||||||
|
short_description: Convert X.509 certificates
|
||||||
|
version_added: 2.19.0
|
||||||
|
description:
|
||||||
|
- This module allows to convert X.509 certificates between different formats.
|
||||||
|
author:
|
||||||
|
- Felix Fontein (@felixfontein)
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- ansible.builtin.files
|
||||||
|
- community.crypto.attributes
|
||||||
|
- community.crypto.attributes.files
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: none
|
||||||
|
safe_file_operations:
|
||||||
|
support: full
|
||||||
|
options:
|
||||||
|
src_path:
|
||||||
|
description:
|
||||||
|
- Name of the file containing the X.509 certificate to convert.
|
||||||
|
- Exactly one of O(src_path) or O(src_content) must be specified.
|
||||||
|
type: path
|
||||||
|
src_content:
|
||||||
|
description:
|
||||||
|
- The content of the file containing the X.509 certificate to convert.
|
||||||
|
- This must be text. If you are not sure that the input file is PEM, you must Base64 encode
|
||||||
|
the value and set O(src_content_base64=true). You can use the
|
||||||
|
P(ansible.builtin.b64encode#filter) filter plugin for this.
|
||||||
|
- Exactly one of O(src_path) or O(src_content) must be specified.
|
||||||
|
type: str
|
||||||
|
src_content_base64:
|
||||||
|
description:
|
||||||
|
- If set to V(true) when O(src_content) is provided, the module assumes that the value
|
||||||
|
of O(src_content) is Base64 encoded.
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
format:
|
||||||
|
description:
|
||||||
|
- Determines which format the destination X.509 certificate 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:
|
||||||
|
- pem
|
||||||
|
- der
|
||||||
|
required: true
|
||||||
|
strict:
|
||||||
|
description:
|
||||||
|
- If the input is a PEM file, ensure that it contains a single PEM object, that
|
||||||
|
the header and footer match, and are of type C(CERTIFICATE) or C(X509 CERTIFICATE).
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
dest_path:
|
||||||
|
description:
|
||||||
|
- Name of the file in which the generated TLS/SSL X.509 certificate will be written.
|
||||||
|
type: path
|
||||||
|
required: true
|
||||||
|
backup:
|
||||||
|
description:
|
||||||
|
- Create a backup file including a timestamp so you can get
|
||||||
|
the original X.509 certificate back if you overwrote it with a new one by accident.
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
seealso:
|
||||||
|
- plugin: ansible.builtin.b64encode
|
||||||
|
plugin_type: filter
|
||||||
|
- module: community.crypto.x509_certificate
|
||||||
|
- module: community.crypto.x509_certificate_pipe
|
||||||
|
- module: community.crypto.x509_certificate_info
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Convert PEM X.509 certificate to DER format
|
||||||
|
community.crypto.x509_certificate_convert:
|
||||||
|
src_path: /etc/ssl/cert/ansible.com.pem
|
||||||
|
dest_path: /etc/ssl/cert/ansible.com.der
|
||||||
|
format: der
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
backup_file:
|
||||||
|
description: Name of backup file created.
|
||||||
|
returned: changed and if O(backup) is V(true)
|
||||||
|
type: str
|
||||||
|
sample: /path/to/cert.pem.2019-03-09@11:22~
|
||||||
|
'''
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||||
|
load_file_if_exists,
|
||||||
|
write_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
|
OpenSSLObjectError,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
|
PEM_START,
|
||||||
|
PEM_END_START,
|
||||||
|
PEM_END,
|
||||||
|
identify_pem_format,
|
||||||
|
split_pem_list,
|
||||||
|
extract_pem,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||||
|
OpenSSLObject,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_certificate(input, strict=False):
|
||||||
|
input_format = 'pem' if identify_pem_format(input) else 'der'
|
||||||
|
if input_format == 'pem':
|
||||||
|
pems = split_pem_list(to_text(input))
|
||||||
|
if len(pems) > 1 and strict:
|
||||||
|
raise ValueError('The input contains {count} PEM objects, expecting only one since strict=true'.format(count=len(pems)))
|
||||||
|
pem_header_type, content = extract_pem(pems[0], strict=strict)
|
||||||
|
if strict and pem_header_type not in ('CERTIFICATE', 'X509 CERTIFICATE'):
|
||||||
|
raise ValueError('type is {type!r}, expecting CERTIFICATE or X509 CERTIFICATE'.format(type=pem_header_type))
|
||||||
|
input = base64.b64decode(content)
|
||||||
|
else:
|
||||||
|
pem_header_type = None
|
||||||
|
return input, input_format, pem_header_type
|
||||||
|
|
||||||
|
|
||||||
|
class X509CertificateConvertModule(OpenSSLObject):
|
||||||
|
def __init__(self, module):
|
||||||
|
super(X509CertificateConvertModule, self).__init__(
|
||||||
|
module.params['dest_path'],
|
||||||
|
'present',
|
||||||
|
False,
|
||||||
|
module.check_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.src_path = module.params['src_path']
|
||||||
|
self.src_content = module.params['src_content']
|
||||||
|
self.src_content_base64 = module.params['src_content_base64']
|
||||||
|
if self.src_content is not None:
|
||||||
|
self.input = to_bytes(self.src_content)
|
||||||
|
if self.src_content_base64:
|
||||||
|
try:
|
||||||
|
self.input = base64.b64decode(self.input)
|
||||||
|
except Exception as exc:
|
||||||
|
module.fail_json(msg='Cannot Base64 decode src_content: {exc}'.format(exc=exc))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open(self.src_path, 'rb') as f:
|
||||||
|
self.input = f.read()
|
||||||
|
except Exception as exc:
|
||||||
|
module.fail_json(msg='Failure while reading file {fn}: {exc}'.format(fn=self.src_path, exc=exc))
|
||||||
|
|
||||||
|
self.format = module.params['format']
|
||||||
|
self.strict = module.params['strict']
|
||||||
|
self.wanted_pem_type = 'CERTIFICATE'
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.input, self.input_format, dummy = parse_certificate(self.input, strict=self.strict)
|
||||||
|
except Exception as exc:
|
||||||
|
module.fail_json(msg='Error while parsing PEM: {exc}'.format(exc=exc))
|
||||||
|
|
||||||
|
self.backup = module.params['backup']
|
||||||
|
self.backup_file = None
|
||||||
|
|
||||||
|
module.params['path'] = self.path
|
||||||
|
|
||||||
|
self.dest_content = load_file_if_exists(self.path, module)
|
||||||
|
self.dest_content_format = None
|
||||||
|
self.dest_content_pem_type = None
|
||||||
|
if self.dest_content is not None:
|
||||||
|
try:
|
||||||
|
self.dest_content, self.dest_content_format, self.dest_content_pem_type = parse_certificate(
|
||||||
|
self.dest_content, strict=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def needs_conversion(self):
|
||||||
|
if self.dest_content is None or self.dest_content_format is None:
|
||||||
|
return True
|
||||||
|
if self.dest_content_format != self.format:
|
||||||
|
return True
|
||||||
|
if self.input != self.dest_content:
|
||||||
|
return True
|
||||||
|
if self.format == 'pem' and self.dest_content_pem_type != self.wanted_pem_type:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_dest_certificate(self):
|
||||||
|
if self.format == 'der':
|
||||||
|
return self.input
|
||||||
|
data = to_bytes(base64.b64encode(self.input))
|
||||||
|
lines = [to_bytes('{0}{1}{2}'.format(PEM_START, self.wanted_pem_type, PEM_END))]
|
||||||
|
lines += [data[i:i + 64] for i in range(0, len(data), 64)]
|
||||||
|
lines.append(to_bytes('{0}{1}{2}\n'.format(PEM_END_START, self.wanted_pem_type, PEM_END)))
|
||||||
|
return b'\n'.join(lines)
|
||||||
|
|
||||||
|
def generate(self, module):
|
||||||
|
"""Do conversion."""
|
||||||
|
if self.needs_conversion():
|
||||||
|
# Convert
|
||||||
|
cert_data = self.get_dest_certificate()
|
||||||
|
if not self.check_mode:
|
||||||
|
if self.backup:
|
||||||
|
self.backup_file = module.backup_local(self.path)
|
||||||
|
write_file(module, cert_data)
|
||||||
|
self.changed = True
|
||||||
|
|
||||||
|
file_args = module.load_file_common_arguments(module.params)
|
||||||
|
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||||
|
self.changed = True
|
||||||
|
else:
|
||||||
|
self.changed = module.set_fs_attributes_if_different(file_args, self.changed)
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
"""Serialize the object into a dictionary."""
|
||||||
|
result = dict(
|
||||||
|
changed=self.changed,
|
||||||
|
)
|
||||||
|
if self.backup_file:
|
||||||
|
result['backup_file'] = self.backup_file
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = dict(
|
||||||
|
src_path=dict(type='path'),
|
||||||
|
src_content=dict(type='str'),
|
||||||
|
src_content_base64=dict(type='bool', default=False),
|
||||||
|
format=dict(type='str', required=True, choices=['pem', 'der']),
|
||||||
|
strict=dict(type='bool', default=False),
|
||||||
|
dest_path=dict(type='path', required=True),
|
||||||
|
backup=dict(type='bool', default=False),
|
||||||
|
)
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
add_file_common_args=True,
|
||||||
|
required_one_of=[('src_path', 'src_content')],
|
||||||
|
mutually_exclusive=[('src_path', 'src_content')],
|
||||||
|
)
|
||||||
|
|
||||||
|
base_dir = os.path.dirname(module.params['dest_path']) or '.'
|
||||||
|
if not os.path.isdir(base_dir):
|
||||||
|
module.fail_json(
|
||||||
|
name=base_dir,
|
||||||
|
msg='The directory %s does not exist or the file is not a directory' % base_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cert = X509CertificateConvertModule(module)
|
||||||
|
cert.generate(module)
|
||||||
|
result = cert.dump()
|
||||||
|
module.exit_json(**result)
|
||||||
|
except OpenSSLObjectError as exc:
|
||||||
|
module.fail_json(msg=to_native(exc))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -21,7 +21,7 @@ description:
|
|||||||
up to version 2.9. When moved to the collection C(community.crypto), it was renamed to
|
up to version 2.9. When moved to the collection C(community.crypto), it was renamed to
|
||||||
M(community.crypto.x509_certificate_info). From Ansible 2.10 on, it can still be used by the
|
M(community.crypto.x509_certificate_info). From Ansible 2.10 on, it can still be used by the
|
||||||
old short name (or by C(ansible.builtin.openssl_certificate_info)), which redirects to
|
old short name (or by C(ansible.builtin.openssl_certificate_info)), which redirects to
|
||||||
C(community.crypto.x509_certificate_info). When using FQCNs or when using the
|
M(community.crypto.x509_certificate_info). When using FQCNs or when using the
|
||||||
L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook)
|
L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook)
|
||||||
keyword, the new name M(community.crypto.x509_certificate_info) should be used to avoid
|
keyword, the new name M(community.crypto.x509_certificate_info) should be used to avoid
|
||||||
a deprecation warning.
|
a deprecation warning.
|
||||||
@@ -39,30 +39,31 @@ options:
|
|||||||
path:
|
path:
|
||||||
description:
|
description:
|
||||||
- Remote absolute path where the certificate file is loaded from.
|
- Remote absolute path where the certificate file is loaded from.
|
||||||
- Either I(path) or I(content) must be specified, but not both.
|
- Either O(path) or O(content) must be specified, but not both.
|
||||||
|
- PEM and DER formats are supported.
|
||||||
type: path
|
type: path
|
||||||
content:
|
content:
|
||||||
description:
|
description:
|
||||||
- Content of the X.509 certificate in PEM format.
|
- Content of the X.509 certificate in PEM format.
|
||||||
- Either I(path) or I(content) must be specified, but not both.
|
- Either O(path) or O(content) must be specified, but not both.
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
valid_at:
|
valid_at:
|
||||||
description:
|
description:
|
||||||
- A dict of names mapping to time specifications. Every time specified here
|
- A dict of names mapping to time specifications. Every time specified here
|
||||||
will be checked whether the certificate is valid at this point. See the
|
will be checked whether the certificate is valid at this point. See the
|
||||||
C(valid_at) return value for informations on the result.
|
RV(valid_at) return value for information on the result.
|
||||||
- Time can be specified either as relative time or as absolute timestamp.
|
- Time can be specified either as relative time or as absolute timestamp.
|
||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (for example C(+32w1d2h)), and ASN.1 TIME (in other words, pattern C(YYYYMMDDHHMMSSZ)).
|
+ C([w | d | h | m | s]) (for example V(+32w1d2h)), and ASN.1 TIME (in other words, pattern C(YYYYMMDDHHMMSSZ)).
|
||||||
Note that all timestamps will be treated as being in UTC.
|
Note that all timestamps will be treated as being in UTC.
|
||||||
type: dict
|
type: dict
|
||||||
select_crypto_backend:
|
select_crypto_backend:
|
||||||
description:
|
description:
|
||||||
- Determines which crypto backend to use.
|
- Determines which crypto backend to use.
|
||||||
- The default choice is C(auto), which tries to use C(cryptography) if available.
|
- The default choice is V(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.
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||||
type: str
|
type: str
|
||||||
default: auto
|
default: auto
|
||||||
choices: [ auto, cryptography ]
|
choices: [ auto, cryptography ]
|
||||||
@@ -73,10 +74,11 @@ notes:
|
|||||||
seealso:
|
seealso:
|
||||||
- module: community.crypto.x509_certificate
|
- module: community.crypto.x509_certificate
|
||||||
- module: community.crypto.x509_certificate_pipe
|
- module: community.crypto.x509_certificate_pipe
|
||||||
- ref: community.crypto.x509_certificate_info filter <ansible_collections.community.crypto.x509_certificate_info_filter>
|
- plugin: community.crypto.x509_certificate_info
|
||||||
# - plugin: community.crypto.x509_certificate_info
|
plugin_type: filter
|
||||||
# plugin_type: filter
|
|
||||||
description: A filter variant of this module.
|
description: A filter variant of this module.
|
||||||
|
- plugin: community.crypto.to_serial
|
||||||
|
plugin_type: filter
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
@@ -114,7 +116,7 @@ EXAMPLES = r'''
|
|||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Validate that certificate is valid tomorrow, but not in three weeks
|
- name: Validate that certificate is valid tomorrow, but not in three weeks
|
||||||
assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- result.valid_at.point_1 # valid in one day
|
- result.valid_at.point_1 # valid in one day
|
||||||
- not result.valid_at.point_2 # not valid in three weeks
|
- not result.valid_at.point_2 # not valid in three weeks
|
||||||
@@ -126,7 +128,7 @@ expired:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
basic_constraints:
|
basic_constraints:
|
||||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
description: Entries in the C(basic_constraints) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -136,7 +138,7 @@ basic_constraints_critical:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
extended_key_usage:
|
extended_key_usage:
|
||||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(extended_key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -167,7 +169,7 @@ extensions_by_oid:
|
|||||||
sample: "MAMCAQU="
|
sample: "MAMCAQU="
|
||||||
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
|
||||||
key_usage:
|
key_usage:
|
||||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
description: Entries in the C(key_usage) extension, or V(none) if extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: [Key Agreement, Data Encipherment]
|
sample: [Key Agreement, Data Encipherment]
|
||||||
@@ -177,8 +179,8 @@ key_usage_critical:
|
|||||||
type: bool
|
type: bool
|
||||||
subject_alt_name:
|
subject_alt_name:
|
||||||
description:
|
description:
|
||||||
- Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
- Entries in the C(subject_alt_name) extension, or V(none) if extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -188,7 +190,7 @@ subject_alt_name_critical:
|
|||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple:
|
ocsp_must_staple:
|
||||||
description: C(true) if the OCSP Must Staple extension is present, C(none) otherwise.
|
description: V(true) if the OCSP Must Staple extension is present, V(none) otherwise.
|
||||||
returned: success
|
returned: success
|
||||||
type: bool
|
type: bool
|
||||||
ocsp_must_staple_critical:
|
ocsp_must_staple_critical:
|
||||||
@@ -239,8 +241,8 @@ public_key:
|
|||||||
public_key_type:
|
public_key_type:
|
||||||
description:
|
description:
|
||||||
- The certificate's public key's type.
|
- The certificate's public key's type.
|
||||||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448).
|
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
|
||||||
- Will start with C(unknown) if the key type cannot be determined.
|
- Will start with V(unknown) if the key type cannot be determined.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
version_added: 1.7.0
|
version_added: 1.7.0
|
||||||
@@ -256,57 +258,57 @@ public_key_data:
|
|||||||
description:
|
description:
|
||||||
- Bit size of modulus (RSA) or prime number (DSA).
|
- Bit size of modulus (RSA) or prime number (DSA).
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA) or C(public_key_type=DSA)
|
returned: When RV(public_key_type=RSA) or RV(public_key_type=DSA)
|
||||||
modulus:
|
modulus:
|
||||||
description:
|
description:
|
||||||
- The RSA key's modulus.
|
- The RSA key's modulus.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(public_key_type=RSA)
|
||||||
exponent:
|
exponent:
|
||||||
description:
|
description:
|
||||||
- The RSA key's public exponent.
|
- The RSA key's public exponent.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=RSA)
|
returned: When RV(public_key_type=RSA)
|
||||||
p:
|
p:
|
||||||
description:
|
description:
|
||||||
- The C(p) value for DSA.
|
- The C(p) value for DSA.
|
||||||
- This is the prime modulus upon which arithmetic takes place.
|
- This is the prime modulus upon which arithmetic takes place.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(public_key_type=DSA)
|
||||||
q:
|
q:
|
||||||
description:
|
description:
|
||||||
- The C(q) value for DSA.
|
- 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
|
- 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.
|
multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(public_key_type=DSA)
|
||||||
g:
|
g:
|
||||||
description:
|
description:
|
||||||
- The C(g) value for DSA.
|
- The C(g) value for DSA.
|
||||||
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=DSA)
|
returned: When RV(public_key_type=DSA)
|
||||||
curve:
|
curve:
|
||||||
description:
|
description:
|
||||||
- The curve's name for ECC.
|
- The curve's name for ECC.
|
||||||
type: str
|
type: str
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(public_key_type=ECC)
|
||||||
exponent_size:
|
exponent_size:
|
||||||
description:
|
description:
|
||||||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(public_key_type=ECC)
|
||||||
x:
|
x:
|
||||||
description:
|
description:
|
||||||
- The C(x) coordinate for the public point on the elliptic curve.
|
- The C(x) coordinate for the public point on the elliptic curve.
|
||||||
type: int
|
type: int
|
||||||
returned: When C(public_key_type=ECC)
|
returned: When RV(public_key_type=ECC)
|
||||||
y:
|
y:
|
||||||
description:
|
description:
|
||||||
- For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
|
- For RV(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.
|
- For RV(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
|
type: int
|
||||||
returned: When C(public_key_type=DSA) or C(public_key_type=ECC)
|
returned: When RV(public_key_type=DSA) or RV(public_key_type=ECC)
|
||||||
public_key_fingerprints:
|
public_key_fingerprints:
|
||||||
description:
|
description:
|
||||||
- Fingerprints of certificate's public key.
|
- Fingerprints of certificate's public key.
|
||||||
@@ -330,7 +332,10 @@ signature_algorithm:
|
|||||||
type: str
|
type: str
|
||||||
sample: sha256WithRSAEncryption
|
sample: sha256WithRSAEncryption
|
||||||
serial_number:
|
serial_number:
|
||||||
description: The certificate's serial number.
|
description:
|
||||||
|
- The certificate's serial number.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 1234
|
sample: 1234
|
||||||
@@ -340,7 +345,7 @@ version:
|
|||||||
type: int
|
type: int
|
||||||
sample: 3
|
sample: 3
|
||||||
valid_at:
|
valid_at:
|
||||||
description: For every time stamp provided in the I(valid_at) option, a
|
description: For every time stamp provided in the O(valid_at) option, a
|
||||||
boolean whether the certificate is valid at that point in time
|
boolean whether the certificate is valid at that point in time
|
||||||
or not.
|
or not.
|
||||||
returned: success
|
returned: success
|
||||||
@@ -348,24 +353,24 @@ valid_at:
|
|||||||
subject_key_identifier:
|
subject_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The certificate's subject key identifier.
|
- The certificate's subject key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
- Is V(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_key_identifier:
|
authority_key_identifier:
|
||||||
description:
|
description:
|
||||||
- The certificate's authority key identifier.
|
- The certificate's authority key identifier.
|
||||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||||
authority_cert_issuer:
|
authority_cert_issuer:
|
||||||
description:
|
description:
|
||||||
- The certificate's authority cert issuer as a list of general names.
|
- The certificate's authority cert issuer as a list of general names.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
- See I(name_encoding) for how IDNs are handled.
|
- See O(name_encoding) for how IDNs are handled.
|
||||||
returned: success
|
returned: success
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
@@ -373,18 +378,20 @@ authority_cert_issuer:
|
|||||||
authority_cert_serial_number:
|
authority_cert_serial_number:
|
||||||
description:
|
description:
|
||||||
- The certificate's authority cert serial number.
|
- The certificate's authority cert serial number.
|
||||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||||
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
||||||
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
||||||
returned: success
|
returned: success
|
||||||
type: int
|
type: int
|
||||||
sample: 12345
|
sample: 12345
|
||||||
ocsp_uri:
|
ocsp_uri:
|
||||||
description: The OCSP responder URI, if included in the certificate. Will be
|
description: The OCSP responder URI, if included in the certificate. Will be
|
||||||
C(none) if no OCSP responder URI is included.
|
V(none) if no OCSP responder URI is included.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
issuer_uri:
|
issuer_uri:
|
||||||
description: The Issuer URI, if included in the certificate. Will be
|
description: The Issuer URI, if included in the certificate. Will be
|
||||||
C(none) if no issuer URI is included.
|
V(none) if no issuer URI is included.
|
||||||
returned: success
|
returned: success
|
||||||
type: str
|
type: str
|
||||||
version_added: 2.9.0
|
version_added: 2.9.0
|
||||||
@@ -399,14 +406,18 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
|
|||||||
OpenSSLObjectError,
|
OpenSSLObjectError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
get_relative_time_option,
|
CRYPTOGRAPHY_TIMEZONE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import (
|
||||||
select_backend,
|
select_backend,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||||
|
get_relative_time_option,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
@@ -444,10 +455,10 @@ def main():
|
|||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v))
|
msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v))
|
||||||
)
|
)
|
||||||
valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k))
|
valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k), with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = module_backend.get_info()
|
result = module_backend.get_info(der_support_enabled=module.params['content'] is None)
|
||||||
|
|
||||||
not_before = module_backend.get_not_before()
|
not_before = module_backend.get_not_before()
|
||||||
not_after = module_backend.get_not_after()
|
not_after = module_backend.get_not_after()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user