diff --git a/plugins/doc_fragments/ca_admin.py b/plugins/doc_fragments/ca_admin.py index d694e853..a084bb0d 100644 --- a/plugins/doc_fragments/ca_admin.py +++ b/plugins/doc_fragments/ca_admin.py @@ -20,7 +20,16 @@ class ModuleDocFragment: type: str aliases: - admin_name + admin_password: + description: > + The password to encrypt or decrypt the private key. + Will be passed to step-cli through a temporary file. + Mutually exclusive with I(admin_password_file) + type: str admin_password_file: - description: The path to the file containing the password to encrypt or decrypt the private key. + description: > + The path to the file containing the password to encrypt or decrypt the private key. + Must already be present on the remote host. + Mutually exclusive with I(admin_password) type: path ''' diff --git a/plugins/module_utils/cli_wrapper.py b/plugins/module_utils/cli_wrapper.py index ea51a6a9..8d5dc5dc 100644 --- a/plugins/module_utils/cli_wrapper.py +++ b/plugins/module_utils/cli_wrapper.py @@ -90,7 +90,7 @@ def build(self, module: AnsibleModule, tmpdir: Path) -> List[str]: # Create temporary files for any parameters that need to point to files, such as password-file # Since these files may contain sensitive data, we first create the fd with locked-down permissions, # then write the actual content - for module_arg in self.module_tmpfile_args: + for module_arg in [arg for arg in self.module_tmpfile_args if module_params[arg]]: path = tmpdir / module_arg path.touch(0o700, exist_ok=False) with open(path, "w", encoding="utf-8") as f: @@ -98,15 +98,13 @@ def build(self, module: AnsibleModule, tmpdir: Path) -> List[str]: args.extend([self.module_tmpfile_args[module_arg], path.as_posix()]) # transform the values in module_params into valid step-coi arguments using module_args_params mapping - for param_name in self.module_param_args: + for param_name in [arg for arg in self.module_param_args if module_params[arg]]: if param_name not in module_params: raise CliError(f"Could not build command parameters: " f"param '{param_name}' not in module argspec, this is most likely a bug") + param_type = module.argument_spec[param_name].get("type", "str") - if not module_params[param_name]: - # param not set - pass - elif param_type == "bool" and bool(module_params[param_name]): + if param_type == "bool" and bool(module_params[param_name]): args.append(self.module_param_args[param_name]) elif param_type == "list": for item in cast(List, module_params[param_name]): @@ -147,7 +145,7 @@ def run(self, module: AnsibleModule) -> CliCommandResult: CliError if the module args don't match with the provided params """ # use a context manager to ensure that our sensitive temporary files are *always* deleted - with tempfile.TemporaryDirectory("ansible-smallstep") as tmpdir: + with tempfile.TemporaryDirectory("-ansible-smallstep") as tmpdir: cmd = [self.executable.path] + self.args.build(module, Path(tmpdir)) if module.check_mode and not self.run_in_check_mode: diff --git a/plugins/module_utils/params/ca_admin.py b/plugins/module_utils/params/ca_admin.py index 284b2730..1a18cc7a 100644 --- a/plugins/module_utils/params/ca_admin.py +++ b/plugins/module_utils/params/ca_admin.py @@ -17,22 +17,29 @@ class AdminParams(ParamsHelper): admin_key=dict(type="path"), admin_provisioner=dict(type="str", aliases=["admin_issuer"]), admin_subject=dict(type="str", aliases=["admin_name"]), + admin_password=dict(type="str", no_log=True), admin_password_file=dict(type="path", no_log=False) ) @classmethod def cli_args(cls) -> CliCommandArgs: - return CliCommandArgs([], {key: f"--{key.replace('_', '-')}" for key in cls.argument_spec}) + return CliCommandArgs([], { + "admin_cert": "--admin-cert", + "admin_key": "--admin-key", + "admin_provisioner": "--admin-provisioner", + "admin_subject": "--admin-subject", + "admin_password_file": "--admin-password-file", + }, { + "admin_password": "--admin-password-file" + }) # pylint: disable=useless-parent-delegation def __init__(self, module: AnsibleModule) -> None: super().__init__(module) def check(self): - try: - validation.check_required_together(["admin_cert", "admin_key"], self.module.params) - except ValueError: - self.module.fail_json(msg="admin_cert and admin_key must be specified together") + validation.check_required_together(["admin_cert", "admin_key"], self.module.params) + validation.check_mutually_exclusive(["admin_password", "admin_password_file"], self.module.params) def is_defined(self): return bool(self.module.params["admin_cert"]) # type: ignore diff --git a/plugins/modules/step_ca_certificate.py b/plugins/modules/step_ca_certificate.py index a17fa1b5..12e1768f 100644 --- a/plugins/modules/step_ca_certificate.py +++ b/plugins/modules/step_ca_certificate.py @@ -126,8 +126,16 @@ - issuer description: The provisioner name to use. Required if I(state=present). type: str + provisioner_password: + description: > + The password to decrypt the one-time token generating key. + Will be passed to step-cli through a temporary file. + Mutually exclusive with I(provisioner_password_file) + type: str provisioner_password_file: - description: The path to the file containing the password to decrypt the one-time token generating key. + description: > + The path to the file containing the password to decrypt the one-time token generating key. + Mutually exclusive with I(provisioner_password) type: path revoke_on_delete: description: If I(state=absent), attempt to revoke the certificate before deleting it @@ -264,7 +272,7 @@ from typing import cast, Dict, Any from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.common.validation import check_required_if +from ansible.module_utils.common.validation import check_required_if, check_mutually_exclusive from ..module_utils.params.ca_connection import CaConnectionParams from ..module_utils.cli_wrapper import CliCommand, CliCommandArgs, StepCliExecutable @@ -297,7 +305,8 @@ def create_certificate(executable: StepCliExecutable, module: AnsibleModule, for if force: args.append("--force") - create_args = CaConnectionParams.cli_args().join(CliCommandArgs(args, cert_cliarg_map)) + create_args = CaConnectionParams.cli_args().join(CliCommandArgs( + args, cert_cliarg_map, {"provisioner_password": "--provisioner-password-file"})) create_cmd = CliCommand(executable, create_args) create_cmd.run(module) return {"changed": True} @@ -408,6 +417,7 @@ def run_module(): not_after=dict(type="str"), not_before=dict(type="str"), provisioner=dict(type="str", aliases=["issuer"]), + provisioner_password=dict(type="str", no_log=True), provisioner_password_file=dict(type="path", no_log=False), revoke_on_delete=dict(type="bool", default=True), revoke_reason=dict(type="str"), @@ -431,11 +441,17 @@ def run_module(): **CaConnectionParams.argument_spec, **argument_spec, }, supports_check_mode=True) - CaConnectionParams(module).check() module_params = cast(Dict, module.params) - check_required_if([ - ["state", "present", ["name", "provisioner"], True], - ], module_params) + + try: + CaConnectionParams(module).check() + check_required_if([ + ["state", "present", ["name", "provisioner"], True], + ], module_params) + check_mutually_exclusive(["provisioner_password", "provisioner_password_file"], module_params) + except TypeError as e: + module.fail_json(f"Parameter validation failed: {e}") + executable = StepCliExecutable(module, module_params["step_cli_executable"]) crt_exists = Path(module_params["crt_file"]).exists() diff --git a/plugins/modules/step_ca_provisioner.py b/plugins/modules/step_ca_provisioner.py index 06f83ec4..0a1e5acc 100644 --- a/plugins/modules/step_ca_provisioner.py +++ b/plugins/modules/step_ca_provisioner.py @@ -164,8 +164,16 @@ version_added: 0.20.0 aliases: - tenant_id + password: + description: > + The password to encrypt or decrypt the private key. + Will be passed to step-cli through a temporary file. + Mutually exclusive with I(password_file) + type: str password_file: - description: The path to the file containing the password to encrypt or decrypt the private key. + description: > + The path to the file containing the password to encrypt or decrypt the private key. + Mutually exclusive with I(password) type: path public_key: description: > @@ -440,6 +448,7 @@ from typing import cast, Dict, Any from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.validation import check_mutually_exclusive from ..module_utils.params.ca_admin import AdminParams from ..module_utils.cli_wrapper import CliCommand, CliCommandArgs, StepCliExecutable @@ -502,12 +511,16 @@ "x509_default_dur": "--x509-default-dur", "x5c_root": "--x5c-root", } +CREATE_UPDATE_TMPFILE_ARGS = { + "password": "--password-file" +} def add_provisioner(name: str, provisioner_type: str, executable: StepCliExecutable, module: AnsibleModule): args = AdminParams.cli_args().join(CliCommandArgs( ["ca", "provisioner", "add", name, "--type", provisioner_type], - {**CREATE_UPDATE_CLIARGS, **CONNECTION_CLIARG_MAP} + {**CREATE_UPDATE_CLIARGS, **CONNECTION_CLIARG_MAP}, + CREATE_UPDATE_TMPFILE_ARGS )) cmd = CliCommand(executable, args) cmd.run(module) @@ -517,7 +530,8 @@ def add_provisioner(name: str, provisioner_type: str, executable: StepCliExecuta def update_provisioner(name: str, executable: StepCliExecutable, module: AnsibleModule): args = AdminParams.cli_args().join(CliCommandArgs( ["ca", "provisioner", "update", name], - {**CREATE_UPDATE_CLIARGS, **CONNECTION_CLIARG_MAP} + {**CREATE_UPDATE_CLIARGS, **CONNECTION_CLIARG_MAP}, + CREATE_UPDATE_TMPFILE_ARGS )) cmd = CliCommand(executable, args) cmd.run(module) @@ -564,6 +578,7 @@ def run_module(): oidc_groups=dict(type="list", elements="str", aliases=["group", "oidc_group"]), oidc_listen_address=dict(type="str", aliases=["listen_address", "oidc_client_address"]), oidc_tenant_id=dict(type="str", aliases=["tenant_id"]), + password=dict(type="str", no_log=True), password_file=dict(type="path", no_log=False), public_key=dict(type="path", aliases=["jwk_public_key", "k8ssa_public_key", "k8s_pem_keys_file"]), require_eab=dict(type="bool"), @@ -598,9 +613,14 @@ def run_module(): **AdminParams.argument_spec, **argument_spec }, supports_check_mode=True) - admin_params = AdminParams(module) - admin_params.check() module_params = cast(Dict, module.params) + admin_params = AdminParams(module) + + try: + admin_params.check() + check_mutually_exclusive(["password", "password_file"], module_params) + except TypeError as e: + module.fail_json(f"Parameter validation failed: {e}") executable = StepCliExecutable(module, module_params["step_cli_executable"]) diff --git a/plugins/modules/step_ca_renew.py b/plugins/modules/step_ca_renew.py index 6c4ef598..7a66f167 100644 --- a/plugins/modules/step_ca_renew.py +++ b/plugins/modules/step_ca_renew.py @@ -38,8 +38,16 @@ output_file: description: The new certificate file path. Defaults to overwriting the crt-file positional argument. type: path + password: + description: > + The password to encrypt or decrypt the private key. + Will be passed to step-cli through a temporary file. + Mutually exclusive with I(password_file) + type: str password_file: - description: The path to the file containing the password to encrypt or decrypt the private key. + description: > + The path to the file containing the password to encrypt or decrypt the private key. + Mutually exclusive with I(password) type: path pid: description: > @@ -76,6 +84,7 @@ from typing import Dict, cast, Any from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.validation import check_mutually_exclusive from ..module_utils.cli_wrapper import CliCommand, CliCommandArgs, StepCliExecutable from ..module_utils.params.ca_connection import CaConnectionParams @@ -90,6 +99,7 @@ def run_module(): exec=dict(type="str"), key_file=dict(type="path", required=True), output_file=dict(type="path"), + password=dict(type="str", no_log=True), password_file=dict(type="path", no_log=False), pid=dict(type="int"), pid_file=dict(type="path"), @@ -101,9 +111,14 @@ def run_module(): **CaConnectionParams.argument_spec, **argument_spec }, supports_check_mode=True) - CaConnectionParams(module).check() module_params = cast(Dict, module.params) + try: + CaConnectionParams(module).check() + check_mutually_exclusive(["password", "password_file"], module_params) + except TypeError as e: + module.fail_json(f"Parameter validation failed: {e}") + executable = StepCliExecutable(module, module_params["step_cli_executable"]) # Regular args @@ -112,7 +127,10 @@ def run_module(): renew_cliarg_map = {arg: f"--{arg.replace('_', '-')}" for arg in renew_cliargs} renew_args = CaConnectionParams.cli_args().join(CliCommandArgs( - ["ca", "renew", module_params["crt_file"], module_params["key_file"]], renew_cliarg_map)) + ["ca", "renew", module_params["crt_file"], module_params["key_file"]], + renew_cliarg_map, + {"password": "--password-file"} + )) renew_cmd = CliCommand(executable, renew_args) renew_res = renew_cmd.run(module) if "Your certificate has been saved in" in renew_res.stderr: diff --git a/plugins/modules/step_ca_token.py b/plugins/modules/step_ca_token.py index f49ee01e..80742ce2 100644 --- a/plugins/modules/step_ca_token.py +++ b/plugins/modules/step_ca_token.py @@ -77,8 +77,16 @@ - issuer description: The provisioner name to use. type: str + provisioner_password: + description: > + The password to encrypt or decrypt the one-time token generating key. + Will be passed to step-cli through a temporary file. + Mutually exclusive with I(password_file) + type: str provisioner_password_file: - description: The path to the file containing the password to decrypt the one-time token generating key. + description: > + The path to the file containing the password to decrypt the one-time token generating key. + Mutually exclusive with I(provisioner_password_file) type: path return_token: description: > @@ -137,8 +145,7 @@ """ from typing import cast, Dict, Any -from ansible.module_utils.common.validation import check_required_one_of -from ansible.module_utils.common.validation import check_mutually_exclusive +from ansible.module_utils.common.validation import check_required_one_of, check_mutually_exclusive from ansible.module_utils.basic import AnsibleModule from ..module_utils.cli_wrapper import CliCommandArgs, StepCliExecutable, CliCommand @@ -161,6 +168,7 @@ def run_module(): output_file=dict(type="path"), principal=dict(type="list", elements="str"), provisioner=dict(type="str", aliases=["issuer"]), + provisioner_password=dict(type="str", no_log=True), provisioner_password_file=dict(type="path", no_log=False), return_token=dict(type="bool"), revoke=dict(type="bool"), @@ -183,15 +191,11 @@ def run_module(): module_params = cast(Dict, module.params) try: - check_mutually_exclusive(["return_token", "output_file"], module.params) - except TypeError: - result["msg"] = "return_token and output_file cannot be specified at the same time" - module.fail_json(**result) - try: - check_required_one_of(["return_token", "output_file"], module.params) - except TypeError: - result["msg"] = "At least one of return_token and output_file must be specified" - module.fail_json(**result) + check_mutually_exclusive(["return_token", "output_file"], module_params) + check_required_one_of(["return_token", "output_file"], module_params) + check_mutually_exclusive(["provisioner_password", "provisioner_password_file"], module_params) + except TypeError as e: + module.fail_json(f"Parameter validation failed: {e}") executable = StepCliExecutable(module, module_params["step_cli_executable"]) @@ -204,7 +208,10 @@ def run_module(): token_cliarg_map = {arg: f"--{arg.replace('_', '-')}" for arg in token_cliargs} token_args = CaConnectionParams.cli_args().join(CliCommandArgs( - ["ca", "token", module_params["name"]], token_cliarg_map)) + ["ca", "token", module_params["name"]], + token_cliarg_map, + {"provisioner_password": "--provisioner-password-file"} + )) token_cmd = CliCommand(executable, token_args) token_res = token_cmd.run(module) diff --git a/tests/integration/targets/step_ca_certificate/tasks/main.yml b/tests/integration/targets/step_ca_certificate/tasks/main.yml index 10e5b8ed..3d2ff80e 100644 --- a/tests/integration/targets/step_ca_certificate/tasks/main.yml +++ b/tests/integration/targets/step_ca_certificate/tasks/main.yml @@ -3,191 +3,191 @@ key_file: "/tmp/key.pem" - block: - - name: Create normal certificate on CA - maxhoesel.smallstep.step_ca_certificate: - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - san: - - foo.bar - kty: RSA - size: 4096 - not_after: 3h + - name: Create normal certificate on CA + maxhoesel.smallstep.step_ca_certificate: + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password_file: "{{ ca_provisioner_password_file }}" + san: + - foo.bar + kty: RSA + size: 4096 + not_after: 3h - - name: Certificate is still present (idempotency check) - maxhoesel.smallstep.step_ca_certificate: - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - san: - - foo.bar - kty: RSA - size: 4096 - not_after: 3h - verify_roots: "/root/.step/certs/root_ca.crt" - register: cert_idempotency - - name: Check that cert did not change - ansible.builtin.assert: - that: not cert_idempotency.changed + - name: Certificate is still present (idempotency check) + maxhoesel.smallstep.step_ca_certificate: + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password_file: "{{ ca_provisioner_password_file }}" + san: + - foo.bar + kty: RSA + size: 4096 + not_after: 3h + verify_roots: "/root/.step/certs/root_ca.crt" + register: cert_idempotency + - name: Check that cert did not change + ansible.builtin.assert: + that: not cert_idempotency.changed - - name: Certificate stays the same if parameters are omitted - maxhoesel.smallstep.step_ca_certificate: - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - verify_roots: "/root/.step/certs/root_ca.crt" - register: cert_missing_parameters_idempotency - - name: Check that cert did not change - ansible.builtin.assert: - that: not cert_missing_parameters_idempotency.changed + - name: Certificate stays the same if parameters are omitted + maxhoesel.smallstep.step_ca_certificate: + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password_file: "{{ ca_provisioner_password_file }}" + verify_roots: "/root/.step/certs/root_ca.crt" + register: cert_missing_parameters_idempotency + - name: Check that cert did not change + ansible.builtin.assert: + that: not cert_missing_parameters_idempotency.changed - - name: Certificate gets reissued on force - maxhoesel.smallstep.step_ca_certificate: - force: true - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - san: - - foo.bar - kty: RSA - size: 4096 - not_after: 3h - verify_roots: "/root/.step/certs/root_ca.crt" - register: cert_force - - name: Check that cert changed - ansible.builtin.assert: - that: cert_force.changed + - name: Certificate gets reissued on force + maxhoesel.smallstep.step_ca_certificate: + force: true + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password: "{{ ca_provisioner_password }}" + san: + - foo.bar + kty: RSA + size: 4096 + not_after: 3h + verify_roots: "/root/.step/certs/root_ca.crt" + register: cert_force + - name: Check that cert changed + ansible.builtin.assert: + that: cert_force.changed - - name: Certificate gets reissued on SAN change - maxhoesel.smallstep.step_ca_certificate: - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - san: - - foo.bar - - another.san - kty: RSA - size: 4096 - not_after: 3h - verify_roots: "/root/.step/certs/root_ca.crt" - register: cert_san - - name: Check that cert changed - ansible.builtin.assert: - that: cert_san.changed + - name: Certificate gets reissued on SAN change + maxhoesel.smallstep.step_ca_certificate: + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password: "{{ ca_provisioner_password }}" + san: + - foo.bar + - another.san + kty: RSA + size: 4096 + not_after: 3h + verify_roots: "/root/.step/certs/root_ca.crt" + register: cert_san + - name: Check that cert changed + ansible.builtin.assert: + that: cert_san.changed - - name: Certificate gets reissued on size change - maxhoesel.smallstep.step_ca_certificate: - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - san: - - foo.bar - - another.san - kty: RSA - size: 2048 - not_after: 3h - verify_roots: "/root/.step/certs/root_ca.crt" - register: cert_size - - name: Check that cert changed - ansible.builtin.assert: - that: cert_size.changed + - name: Certificate gets reissued on size change + maxhoesel.smallstep.step_ca_certificate: + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password: "{{ ca_provisioner_password }}" + san: + - foo.bar + - another.san + kty: RSA + size: 2048 + not_after: 3h + verify_roots: "/root/.step/certs/root_ca.crt" + register: cert_size + - name: Check that cert changed + ansible.builtin.assert: + that: cert_size.changed - - name: Certificate gets reissued on kty change - maxhoesel.smallstep.step_ca_certificate: - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - san: - - foo.bar - - another.san - kty: EC - not_after: 3h - verify_roots: "/root/.step/certs/root_ca.crt" - register: cert_kty - - name: Check that cert changed - ansible.builtin.assert: - that: cert_kty.changed + - name: Certificate gets reissued on kty change + maxhoesel.smallstep.step_ca_certificate: + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password: "{{ ca_provisioner_password }}" + san: + - foo.bar + - another.san + kty: EC + not_after: 3h + verify_roots: "/root/.step/certs/root_ca.crt" + register: cert_kty + - name: Check that cert changed + ansible.builtin.assert: + that: cert_kty.changed - - name: Certificate gets reissued on crv change - maxhoesel.smallstep.step_ca_certificate: - name: "127.0.0.1" - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - provisioner: "{{ ca_provisioner }}" - provisioner_password_file: "{{ ca_provisioner_password_file }}" - san: - - foo.bar - - another.san - kty: EC - crv: P-521 - not_after: 3h - verify_roots: "/root/.step/certs/root_ca.crt" - register: cert_crv - - name: Check that cert changed - ansible.builtin.assert: - that: cert_crv.changed + - name: Certificate gets reissued on crv change + maxhoesel.smallstep.step_ca_certificate: + name: "127.0.0.1" + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + provisioner: "{{ ca_provisioner }}" + provisioner_password: "{{ ca_provisioner_password }}" + san: + - foo.bar + - another.san + kty: EC + crv: P-521 + not_after: 3h + verify_roots: "/root/.step/certs/root_ca.crt" + register: cert_crv + - name: Check that cert changed + ansible.builtin.assert: + that: cert_crv.changed - - name: Revoke certificate - maxhoesel.smallstep.step_ca_certificate: - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - state: revoked - register: revoked - - name: Check that cert got revoked - ansible.builtin.assert: - that: revoked.changed + - name: Revoke certificate + maxhoesel.smallstep.step_ca_certificate: + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + state: revoked + register: revoked + - name: Check that cert got revoked + ansible.builtin.assert: + that: revoked.changed - - name: Revoke certificate again - maxhoesel.smallstep.step_ca_certificate: - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - state: revoked - register: revoked_again - - name: Check that cert revocation didn't change - ansible.builtin.assert: - that: not revoked_again.changed + - name: Revoke certificate again + maxhoesel.smallstep.step_ca_certificate: + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + state: revoked + register: revoked_again + - name: Check that cert revocation didn't change + ansible.builtin.assert: + that: not revoked_again.changed - - name: Delete certificate - maxhoesel.smallstep.step_ca_certificate: - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - revoke_on_delete: true # already the default - state: absent - register: deleted - - name: Check that cert got deleted - ansible.builtin.assert: - that: revoked.changed + - name: Delete certificate + maxhoesel.smallstep.step_ca_certificate: + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + revoke_on_delete: true # already the default + state: absent + register: deleted + - name: Check that cert got deleted + ansible.builtin.assert: + that: revoked.changed - - name: Delete certificate again - maxhoesel.smallstep.step_ca_certificate: - crt_file: "{{ crt_file }}" - key_file: "{{ key_file }}" - revoke_on_delete: true # already the default - state: absent - register: deleted_again - - name: Check that cert did not change - ansible.builtin.assert: - that: not deleted_again.changed + - name: Delete certificate again + maxhoesel.smallstep.step_ca_certificate: + crt_file: "{{ crt_file }}" + key_file: "{{ key_file }}" + revoke_on_delete: true # already the default + state: absent + register: deleted_again + - name: Check that cert did not change + ansible.builtin.assert: + that: not deleted_again.changed always: - - name: Delete generated files - file: - path: "{{ item }}" - state: absent - loop: - - "{{ crt_file }}" - - "{{ key_file }}" + - name: Delete generated files + file: + path: "{{ item }}" + state: absent + loop: + - "{{ crt_file }}" + - "{{ key_file }}" diff --git a/tests/integration/targets/step_ca_provisioner/tasks/main.yml b/tests/integration/targets/step_ca_provisioner/tasks/main.yml index 4f898908..c969aa38 100644 --- a/tests/integration/targets/step_ca_provisioner/tasks/main.yml +++ b/tests/integration/targets/step_ca_provisioner/tasks/main.yml @@ -1,162 +1,166 @@ - block: - - name: Testing keys are present - copy: - src: "{{ item }}" - dest: "/tmp/" - owner: "{{ ca_user }}" - group: "{{ ca_user }}" - mode: 0600 - loop: - - tests_key - - tests_crt + - name: Testing keys are present + copy: + src: "{{ item }}" + dest: "/tmp/" + owner: "{{ ca_user }}" + group: "{{ ca_user }}" + mode: 0600 + loop: + - tests_key + - tests_crt - - name: Testing password file is present - copy: - content: "password-testing" - dest: "/tmp/tests_passfile" - owner: "{{ ca_user }}" - group: "{{ ca_user }}" - mode: 0644 # needs to be readable by the client requesting the cert + - name: Testing password file is present + copy: + content: "password-testing" + dest: "/tmp/tests_passfile" + owner: "{{ ca_user }}" + group: "{{ ca_user }}" + mode: 0644 # needs to be readable by the client requesting the cert - # The values for these test provisioners are mostly identical to the ones in the smallstep documentation, - # so many of the online provisioners do not actually work. Good enough to test our module - # functionality still. - - name: Create test provisioners - maxhoesel.smallstep.step_ca_provisioner: "{{ item | combine({'step_cli_executable': cli_binary}) }}" - loop: - - name: tests-JWK - type: JWK - password_file: "/tmp/tests_passfile" - create: yes - - name: tests-OIDC - type: OIDC - oidc_client_id: 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com - oidc_configuration_endpoint: https://accounts.google.com/.well-known/openid-configuration - oidc_admin_email: - - mariano@smallstep.com - - max@smallstep.com - - name: tests-Amazon - type: AWS - aws_account: 123456789 - instance_age: 1h - - name: tests-Google - type: GCP - gcp_service_account: - - 1234567890-compute@developer.gserviceaccount.com - - 9876543210-compute@developer.gserviceaccount.com - gcp_project: - - identity - - accounting - - name: tests-ACME - type: ACME - - name: tests-x5c - type: X5C - x5c_root_file: "/tmp/tests_crt" - - name: tests-k8s - type: K8SSA - k8s_pem_keys_file: "/tmp/tests_crt" + # The values for these test provisioners are mostly identical to the ones in the smallstep documentation, + # so many of the online provisioners do not actually work. Good enough to test our module + # functionality still. + - name: Create test provisioners + maxhoesel.smallstep.step_ca_provisioner: "{{ item | combine({'step_cli_executable': cli_binary}) }}" + loop: + - name: tests-JWK + type: JWK + password_file: "/tmp/tests_passfile" + create: yes + - name: tests-JWK-passfile + type: JWK + password: "flightofthefirebird" + create: yes + - name: tests-OIDC + type: OIDC + oidc_client_id: 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com + oidc_configuration_endpoint: https://accounts.google.com/.well-known/openid-configuration + oidc_admin_email: + - mariano@smallstep.com + - max@smallstep.com + - name: tests-Amazon + type: AWS + aws_account: 123456789 + instance_age: 1h + - name: tests-Google + type: GCP + gcp_service_account: + - 1234567890-compute@developer.gserviceaccount.com + - 9876543210-compute@developer.gserviceaccount.com + gcp_project: + - identity + - accounting + - name: tests-ACME + type: ACME + - name: tests-x5c + type: X5C + x5c_root_file: "/tmp/tests_crt" + - name: tests-k8s + type: K8SSA + k8s_pem_keys_file: "/tmp/tests_crt" + + - name: Test creation idempotency + maxhoesel.smallstep.step_ca_provisioner: "{{ item | combine({'step_cli_executable': cli_binary })}}" + loop: + - name: tests-JWK + type: JWK + create: yes + password_file: "/tmp/tests_passfile" + - name: tests-OIDC + type: OIDC + oidc_client_id: 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com + oidc_configuration_endpoint: https://accounts.google.com/.well-known/openid-configuration + oidc_admin_email: + - mariano@smallstep.com + - max@smallstep.com + - name: tests-Amazon + type: AWS + aws_account: 123456789 + instance_age: 1h + - name: tests-Google + type: GCP + gcp_service_account: + - 1234567890-compute@developer.gserviceaccount.com + - 9876543210-compute@developer.gserviceaccount.com + gcp_project: + - identity + - accounting + - name: tests-ACME + type: ACME + - name: tests-x5c + type: X5C + x5c_root_file: "/tmp/tests_crt" + - name: tests-k8s + type: K8SSA + k8s_pem_keys_file: "/tmp/tests_crt" + register: second_run + + - name: Verify that nothing changed on the second run + assert: + that: not second_run.changed - - name: Test creation idempotency - maxhoesel.smallstep.step_ca_provisioner: "{{ item | combine({'step_cli_executable': cli_binary })}}" - loop: - - name: tests-JWK - type: JWK - create: yes - password_file: "/tmp/tests_passfile" - - name: tests-OIDC + - name: Test updating provisioners + maxhoesel.smallstep.step_ca_provisioner: + name: tests-OIDC type: OIDC oidc_client_id: 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com oidc_configuration_endpoint: https://accounts.google.com/.well-known/openid-configuration oidc_admin_email: - mariano@smallstep.com - max@smallstep.com - - name: tests-Amazon - type: AWS - aws_account: 123456789 - instance_age: 1h - - name: tests-Google - type: GCP - gcp_service_account: - - 1234567890-compute@developer.gserviceaccount.com - - 9876543210-compute@developer.gserviceaccount.com - gcp_project: - - identity - - accounting - - name: tests-ACME - type: ACME - - name: tests-x5c - type: X5C - x5c_root_file: "/tmp/tests_crt" - - name: tests-k8s - type: K8SSA - k8s_pem_keys_file: "/tmp/tests_crt" - register: second_run - - - name: Verify that nothing changed on the second run - assert: - that: not second_run.changed - - - name: Test updating provisioners - maxhoesel.smallstep.step_ca_provisioner: - name: tests-OIDC - type: OIDC - oidc_client_id: 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com - oidc_configuration_endpoint: https://accounts.google.com/.well-known/openid-configuration - oidc_admin_email: - - mariano@smallstep.com - - max@smallstep.com - - new@admin.com - state: "updated" - step_cli_executable: "{{ cli_binary }}" - register: update_test + - new@admin.com + state: "updated" + step_cli_executable: "{{ cli_binary }}" + register: update_test - - name: Verify that provisioner got updated - ansible.builtin.assert: - that: - update_test.changed + - name: Verify that provisioner got updated + ansible.builtin.assert: + that: update_test.changed - # Remove the online provisioners before restarting as they may imapct server - # functionality - - name: Remove online provisioners - maxhoesel.smallstep.step_ca_provisioner: - name: "{{ item.0 }}" - type: "{{ item.1 }}" - state: absent - step_cli_executable: "{{ cli_binary }}" - loop: - - ["tests-OIDC", "OIDC"] - - ["tests-Amazon", "AWS"] - - ["tests-Google", "GCP"] + # Remove the online provisioners before restarting as they may imapct server + # functionality + - name: Remove online provisioners + maxhoesel.smallstep.step_ca_provisioner: + name: "{{ item.0 }}" + type: "{{ item.1 }}" + state: absent + step_cli_executable: "{{ cli_binary }}" + loop: + - ["tests-OIDC", "OIDC"] + - ["tests-Amazon", "AWS"] + - ["tests-Google", "GCP"] - - name: Get Server PID - shell: pgrep -fa step-ca | grep -v step-ca.sh | cut -d ' ' -f 1 - register: _pid - - name: Reload Server - command: "kill -1 {{ _pid.stdout_lines[0] }}" - become: false + - name: Get Server PID + shell: pgrep -fa step-ca | grep -v step-ca.sh | cut -d ' ' -f 1 + register: _pid + - name: Reload Server + command: "kill -1 {{ _pid.stdout_lines[0] }}" + become: false - - name: Check server health - command: "{{ cli_binary }} ca health" - changed_when: no + - name: Check server health + command: "{{ cli_binary }} ca health" + changed_when: no - - name: Remove test provisioners - maxhoesel.smallstep.step_ca_provisioner: - name: "{{ item }}" - state: absent - step_cli_executable: "{{ cli_binary }}" - loop: - - "tests-JWK" - - "tests-ACME" - - "tests-x5c" - - "tests-k8s" + - name: Remove test provisioners + maxhoesel.smallstep.step_ca_provisioner: + name: "{{ item }}" + state: absent + step_cli_executable: "{{ cli_binary }}" + loop: + - "tests-JWK" + - "tests-JWK-passfile" + - "tests-ACME" + - "tests-x5c" + - "tests-k8s" - - name: Get step-ca config - command: "cat {{ ca_path }}/config/ca.json" - register: step_ca_config - - name: Verify that all provisioners are absent - assert: - that: - - (step_ca_config.stdout | from_json).authority.provisioners is not defined + - name: Get step-ca config + command: "cat {{ ca_path }}/config/ca.json" + register: step_ca_config + - name: Verify that all provisioners are absent + assert: + that: + - (step_ca_config.stdout | from_json).authority.provisioners is not defined become: yes become_user: "{{ ca_user }}" environment: diff --git a/tests/integration/targets/step_ca_renew/tasks/main.yml b/tests/integration/targets/step_ca_renew/tasks/main.yml index 4e27a183..15a7bdd9 100644 --- a/tests/integration/targets/step_ca_renew/tasks/main.yml +++ b/tests/integration/targets/step_ca_renew/tasks/main.yml @@ -12,6 +12,7 @@ maxhoesel.smallstep.step_ca_renew: crt_file: /tmp/generated_certificate key_file: /tmp/generated_key + password_file: "{{ ca_provisioner_password_file }}" expires_in: 5m force: yes register: early_renewal @@ -24,6 +25,7 @@ maxhoesel.smallstep.step_ca_renew: crt_file: /tmp/generated_certificate key_file: /tmp/generated_key + password: "{{ ca_provisioner_password }}" force: yes expires_in: 61m register: forced_renewal diff --git a/tests/integration/targets/step_ca_token/tasks/main.yml b/tests/integration/targets/step_ca_token/tasks/main.yml index b39818b2..a48cc106 100644 --- a/tests/integration/targets/step_ca_token/tasks/main.yml +++ b/tests/integration/targets/step_ca_token/tasks/main.yml @@ -9,3 +9,11 @@ - name: Verify that token got returned assert: that: generated_token.token + +- name: Test token creation with direct provisioner password + maxhoesel.smallstep.step_ca_token: + name: "127.0.0.1" + provisioner: "{{ ca_provisioner }}" + provisioner_password: "{{ ca_provisioner_password }}" + return_token: yes + register: generated_token