-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(step_ca_renew): add ca_renew module (#42)
- Loading branch information
Showing
5 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright: (c) 2021, Max Hösel <ansible@maxhoesel.de> | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
DOCUMENTATION = r""" | ||
--- | ||
module: step_ca_renew | ||
author: Max Hösel (@maxhoesel) | ||
short_description: Renew a valid certificate | ||
version_added: '0.3.0' | ||
description: Renew a valid certificate | ||
requirements: | ||
- A C(step-ca) server, either remote or local | ||
notes: | ||
- Check mode is supported. | ||
options: | ||
crt_file: | ||
description: The certificate in PEM format that we want to renew. | ||
required: yes | ||
type: path | ||
expires_in: | ||
description: > | ||
The amount of time remaining before certificate expiration, at which point a renewal should be attempted. | ||
The certificate renewal will not be performed if the time to expiration is greater than the I(expires_in) value. | ||
A random jitter (duration/20) will be added to avoid multiple services hitting the renew endpoint at the same time. | ||
The duration is a sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". | ||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". | ||
type: str | ||
force: | ||
description: Force the overwrite of files without asking. | ||
type: bool | ||
exec: | ||
description: The command to run after the certificate has been renewed. | ||
type: str | ||
key_file: | ||
description: They key file of the certificate. | ||
required: yes | ||
type: path | ||
output_file: | ||
description: The new certificate file path. Defaults to overwriting the crt-file positional argument. | ||
type: path | ||
password_file: | ||
description: The path to the file containing the password to encrypt or decrypt the private key. | ||
type: path | ||
pid: | ||
description: > | ||
The process id to signal after the certificate has been renewed. By default the the SIGHUP (1) signal will be used, | ||
but this can be configured with the I(signal) parameter. | ||
type: int | ||
pid_file: | ||
description: > | ||
The path from which to read the process id that will be signaled after the certificate has been renewed. | ||
By default the the SIGHUP (1) signal will be used, but this can be configured with the I(signal) parameter. | ||
type: path | ||
signal: | ||
description: > | ||
The signal number to send to the selected PID, so it can reload the configuration and load the new certificate. | ||
Default value is SIGHUP (1). | ||
type: int | ||
extends_documentation_fragment: | ||
- maxhoesel.smallstep.step_cli | ||
- maxhoesel.smallstep.ca_remote_local | ||
""" | ||
|
||
EXAMPLES = r""" | ||
# See https://smallstep.com/docs/step-cli/reference/ca/renew for more examples | ||
- name: Renew a certificate | ||
maxhoesel.smallstep.step_ca_renew: | ||
crt_file: internal.crt | ||
key_file: internal.key | ||
ca_url: https://ca.smallstep.com:9000 | ||
force: yes | ||
""" | ||
|
||
from ansible.module_utils.basic import AnsibleModule | ||
from ..module_utils.validation import check_step_cli_install | ||
from ..module_utils.run import run_step_cli_command | ||
|
||
|
||
def run_module(): | ||
module_args = dict( | ||
crt_file=dict(type="path", required=True), | ||
expires_in=dict(type="str"), | ||
force=dict(type="bool"), | ||
exec=dict(type="str"), | ||
key_file=dict(type="path", required=True), | ||
output_file=dict(type="path"), | ||
password_file=dict(type="path", no_log=False), | ||
pid=dict(type="int"), | ||
pid_file=dict(type="path"), | ||
signal=dict(type="int"), | ||
root=dict(type="path"), | ||
step_cli_executable=dict(type="path", default="step-cli"), | ||
ca_url=dict(type="str"), | ||
ca_config=dict(type="path"), | ||
offline=dict(type="bool"), | ||
) | ||
result = dict(changed=False, stdout="", stderr="", msg="") | ||
module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) | ||
|
||
check_step_cli_install(module, module.params["step_cli_executable"], result) | ||
|
||
# Positional Parameters | ||
params = ["ca", "renew", module.params["crt_file"], module.params["key_file"]] | ||
# Regular args | ||
args = ["expires_in", "force", "exec", "output_file", "password_file", "pid", "pid_file", | ||
"signal", "root", "ca_url", "ca_config", "offline"] | ||
# All parameters can be converted to a mapping by just appending -- and replacing the underscores | ||
args = {arg: "--{a}".format(a=arg.replace("_", "-")) for arg in args} | ||
|
||
result = run_step_cli_command( | ||
module.params["step_cli_executable"], params, | ||
module, result, args | ||
) | ||
if "Your certificate has been saved in" in result["stderr"]: | ||
result["changed"] = True | ||
module.exit_json(**result) | ||
|
||
|
||
def main(): | ||
run_module() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIBaDCCAQ2gAwIBAgIQFMqlUCL64sx6g0MV8OZnhDAKBggqhkjOPQQDAjASMRAw | ||
DgYDVQQDEwdyb290LWNhMB4XDTIwMTExMDIzMzUwMloXDTMwMTEwODIzMzUwMlow | ||
EjEQMA4GA1UEAxMHcm9vdC1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAtY | ||
ZTIL4F+MjNgdrtshVTQ6Kc/yPB8/tfm+i0CXMNq/133ygQTeuZtbC7sZUpLGK86t | ||
rLadRmwJjrcpW+alYy2jRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG | ||
AQH/AgEBMB0GA1UdDgQWBBQYL8oI1OD5GwqHsrc1Nh+aTc2dEjAKBggqhkjOPQQD | ||
AgNJADBGAiEAxN0zGIjVNrs0ifLYJz3p+LUv1sl+ACkgHLjo/C5gIwcCIQDu+iJZ | ||
x27/c6kqM6jTz2vrQY21ylCfq9KN5Zw23F3qSw== | ||
-----END CERTIFICATE----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-----BEGIN EC PRIVATE KEY----- | ||
MHcCAQEEIET5OC19GaXMueCJM6GPSvUKMfnwa4FCZMps/2VhmC/+oAoGCCqGSM49 | ||
AwEHoUQDQgAEC1hlMgvgX4yM2B2u2yFVNDopz/I8Hz+1+b6LQJcw2r/XffKBBN65 | ||
m1sLuxlSksYrzq2stp1GbAmOtylb5qVjLQ== | ||
-----END EC PRIVATE KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dependencies: | ||
- setup_smallstep |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
- name: Testing keys are present | ||
copy: | ||
src: "{{ item }}" | ||
dest: "/tmp/" | ||
owner: "{{ step_ca_user }}" | ||
group: "{{ step_ca_user }}" | ||
mode: 0600 | ||
become: yes | ||
become_user: "{{ step_ca_user }}" | ||
loop: | ||
- tests_key | ||
- tests_crt | ||
- name: Testing password file is present | ||
copy: | ||
content: "{{ step_ca_password }}" | ||
dest: "/tmp/tests_passfile" | ||
owner: "{{ step_ca_user }}" | ||
group: "{{ step_ca_user }}" | ||
mode: 0600 | ||
become: yes | ||
become_user: "{{ step_ca_user }}" | ||
- name: Create test provisioners | ||
maxhoesel.smallstep.step_ca_provisioner: "{{ item }}" | ||
become: yes | ||
become_user: "{{ step_ca_user }}" | ||
environment: | ||
STEPPATH: "{{ step_ca_path }}" | ||
loop: | ||
- name: tests-JWK | ||
type: JWK | ||
jwk_password_file: "/tmp/tests_passfile" | ||
- name: Reload Server | ||
service: | ||
name: step-ca | ||
state: reloaded | ||
|
||
- name: Get token for certificate creation | ||
maxhoesel.smallstep.step_ca_token: | ||
name: "127.0.0.1" | ||
provisioner: tests-JWK | ||
provisioner_password_file: "/tmp/tests_passfile" | ||
return_token: yes | ||
become: yes | ||
become_user: "{{ step_ca_user }}" | ||
register: generated_token | ||
environment: | ||
STEPPATH: "{{ step_ca_path }}" | ||
- name: Create certificate with token | ||
maxhoesel.smallstep.step_ca_certificate: | ||
name: "127.0.0.1" | ||
crt_file: /tmp/generated_certificate | ||
key_file: /tmp/generated_key | ||
provisioner: tests-JWK | ||
provisioner_password_file: "/tmp/tests_passfile" | ||
force: yes | ||
not_after: 1h | ||
token: "{{ generated_token.token }}" | ||
ca_url: "{{ step_ca_url }}" | ||
|
||
- name: Try to renew early | ||
maxhoesel.smallstep.step_ca_renew: | ||
crt_file: /tmp/generated_certificate | ||
key_file: /tmp/generated_key | ||
ca_url: "{{ step_ca_url }}" | ||
expires_in: 5m | ||
force: yes | ||
register: early_renewal | ||
- name: Verify that early renew didn't change anything | ||
assert: | ||
that: not early_renewal.changed | ||
- name: Force renewal of the cert | ||
maxhoesel.smallstep.step_ca_renew: | ||
crt_file: /tmp/generated_certificate | ||
key_file: /tmp/generated_key | ||
ca_url: "{{ step_ca_url }}" | ||
force: yes | ||
expires_in: 61m | ||
register: forced_renewal | ||
- name: Verify that forced renewal worked | ||
assert: | ||
that: forced_renewal.changed | ||
|
||
- name: Delete generated files | ||
file: | ||
path: "{{ item }}" | ||
state: absent | ||
loop: | ||
- /tmp/generated_certificate | ||
- /tmp/generated_key | ||
- /tmp/tests_passfile | ||
- name: Remove test provisioners | ||
maxhoesel.smallstep.step_ca_provisioner: | ||
name: "{{ item.0 }}" | ||
type: "{{ item.1 }}" | ||
state: absent | ||
become: yes | ||
become_user: "{{ step_ca_user }}" | ||
environment: | ||
STEPPATH: "{{ step_ca_path }}" | ||
loop: | ||
- ["tests-JWK", "JWK"] |