Coder Social home page Coder Social logo

maxhoesel-ansible / ansible-collection-smallstep Goto Github PK

View Code? Open in Web Editor NEW
66.0 3.0 20.0 873 KB

Unofficial Ansible Collection for Smallstep CLI and the step-ca server

Home Page: https://github.com/smallstep

License: GNU General Public License v3.0

Python 93.54% Shell 1.60% Jinja 3.89% Dockerfile 0.97%
ansible smallstep ansible-collection

ansible-collection-smallstep's People

Contributors

danielsreichenbach avatar dependabot[bot] avatar eengstrom avatar github-actions[bot] avatar lecrisut avatar maxhoesel avatar nbrew avatar patsevanton avatar pre-commit-ci[bot] avatar remogeissbuehler avatar renovate[bot] avatar scronkfinkle avatar web-flow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ansible-collection-smallstep's Issues

"Module remote_tmp /etc/step-ca/.ansible/tmp did not exist [...]"

When applying the step_ca role of this collection for the first time:

TASK [maxhoesel.smallstep.step_ca : Initialize CA] *****************************
[WARNING]: Module remote_tmp /etc/step-ca/.ansible/tmp did not exist and was
created with a mode of 0700, this may cause issues when running as another
user. To avoid this, create the remote_tmp dir with the correct permissions
manually

I saw this warning also for other ansible playbooks and roles. Is this something that has to be ensured globally, or should/can this step_ca role/this collection itself ensure that the directory is first created with proper permissions?

Must be name 'acme' (lowercase)

The provisioner name for ACME must be in lowercase acme, otherwise step CA will not find the provisioner (certbot uses the lowercase variant).

    - name: Add ACME provisioner
      maxhoesel.smallstep.step_ca_provisioner:
        name: acme
        type: ACME
      become: yes
      become_user: "{{ step_ca_user }}"

Currently this is not the case in the prepare.yml file:
https://github.com/maxhoesel/ansible-collection-smallstep/blob/4e4e0a375db8fe76aebdf8210546a4001873a6b2/roles/step_acme_cert/molecule/default/prepare.yml#L24-L29

It may also be a good idea to add this to the README as an example for adding the ACME provisioner.

/etc/step-ca/.ansible/tmp has the wrong permission

When running the example ca.yml playbook for the first time on a fresh vm, there is an error from Ansible:

TASK [maxhoesel.smallstep.step_ca : Initialize CA] ********************************************************************************************************************************************************************************************************************************************
[WARNING]: Module remote_tmp /etc/step-ca/.ansible/tmp did not exist and was created with a mode of 0700, this may cause issues when running as another user. To avoid this, create the remote_tmp dir with the correct permissions manually

The Step Remove initial provisioner then fails:

TASK [maxhoesel.smallstep.step_ca : Remove initial provisioner] *******************************************************************************************************************************************************************************************************************************
fatal: [manager-1]: FAILED! => {"changed": false, "msg": "Error running command 'step-cli ca provisioner remove tmp_provisioner --ca-config=/etc/step-ca/config/ca.json'. See stderr for details.", "stderr": "client GET https://manager-1.example.local/admin/admins failed: dial tcp 10.254.254.240:443: connect: connection refused\n", "stderr_lines": ["client GET https://manager-1.example.local/admin/admins failed: dial tcp 10.254.254.240:443: connect: connection refused"], "stdout": "", "stdout_lines": []}

Running the playbook a second time is without error and everything is setup.

README.md needs to be updated

The README hasn't yet been changed to include all the changes in from 0.3_rework. This should be fixed soon-ish.

Also a good opportunity to update the tests badge if needed

support alternative location for STEP configuration, effectively the specification of STEPPATH

By default, STEP wants to look in $HOME/.step for configuration files, but they also allow the specification of the STEPPATH environment variable. It would be nice to be able to specify that in an Ansible variable and therefore be able to move the directory out of a user's home directory, into a more typical system directory - e.g. /etc/step

It looks like there are a number of locations where the assumption about /root/.step or $HOME/.step is hard coded, and perhaps a few other where it's implicit, though I don't think it will be hard to fully exercise the code and fix this.

I'm curious your thoughts before I proceed, however.

provisioners not set in config

The provisioners are not set in the small step CA server configuration (ca.json) and config defaults (defaults.json) -
hence the CA server can't handle any requests (as ACME using certbot).
I also set step_acme_cert_ca_provisioner explicitly to ACME, but it is still not used in the CA server configs,
also after purging all step CA server related files (using a fresh VM).

Playbook used for the host localca:

- hosts: localca
  become: yes

  tasks:
    - name: Install step-ca (local CA)
      include_role:
        name: maxhoesel.smallstep.step_ca
      vars:
        step_acme_cert_ca_provisioner: "ACME"

        # CA info
        step_ca_name: Test Dev CA
        step_ca_root_password: "test"
        step_ca_intermediate_password: "test"

Resulting ca.json - no authority/provisioner is present:

{
	"root": "/etc/step-ca/certs/root_ca.crt",
	"federatedRoots": null,
	"crt": "/etc/step-ca/certs/intermediate_ca.crt",
	"key": "/etc/step-ca/secrets/intermediate_ca_key",
	"address": ":443",
	"insecureAddress": "",
	"dnsNames": [
		"localca",
		"10.0.2.15"
	],
	"logger": {
		"format": "text"
	},
	"db": {
		"type": "badgerv2",
		"dataSource": "/etc/step-ca/db",
		"badgerFileLoadingMode": ""
	},
	"authority": {
		"template": {},
		"backdate": "1m0s"
	},
	"tls": {
		"cipherSuites": [
			"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
			"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
		],
		"minVersion": 1.2,
		"maxVersion": 1.3,
		"renegotiation": false
	}
}

defaults.json:

{
	"ca-url": "https://localca",
	"ca-config": "/etc/step-ca/config/ca.json",
	"fingerprint": "[...]",
	"root": "/etc/step-ca/certs/root_ca.crt"
}

authority/provisioners part of another, manually created ca.json configuration -
note the ACME provisioner besides the other JWK provisioner.

	"authority": {
		"provisioners": [
			{
				"type": "JWK",
				"name": "dev@localca",
				"key": {
					"use": "sig",
					"kty": "EC",
					"kid": "[...]",
					"crv": "P-256",
					"alg": "ES256",
					"x": "[...]",
					"y": "[...]-bABdOu2f9KtbyZ_hXTKPUjtNM"
				},
				"encryptedKey": "[...]"
			},
			{
				"type": "ACME",
				"name": "acme"
			}
		]
	},

`step_ca` role throws error in check mode

While running in check mode, I am running across the following error:

TASK [maxhoesel.smallstep.step_ca : Set latest release version] [CHECK MODE] ******************************************************************************************************************
fatal: [steptest1]: FAILED! => {}

MSG:

The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'json'

The error appears to be in '/ansible/dev/collections/ansible_collections/maxhoesel/smallstep/roles/step_ca/tasks/main.yml': line 16, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

      delay: 5
    - name: Set latest release version
      ^ here

This appears to stem from the following block:

- block:
    - name: Get latest step-ca release information
      uri:
        url: https://api.github.com/repos/smallstep/certificates/releases/latest
        body_format: json
        return_content: yes
      register: step_ca_latest_release
      delegate_to: localhost
      become: no
      run_once: yes
      retries: 3
      delay: 5
    - name: Set latest release version
      set_fact:
        step_ca_version: "{{ (step_ca_latest_release.json.tag_name)[1:] }}"
  when: step_ca_version == 'latest'

adding check_mode: no under the when: appears to resolve the issue

Adding provisioner: "SyntaxError: invalid syntax"

When adding an acme provisioner, ansible fails with SyntaxError: invalid syntax error:

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: SyntaxError: invalid syntax
fatal: [localca]: FAILED! => {"changed": false, "module_stderr": "Shared connection to [...] closed.\r\n", "module_stdout": "Traceback (most recent call last):\r\n  File \"/var/tmp/ansible-tmp-1630082859.6666074-4612-147676839279342/AnsiballZ_step_ca_provisioner.py\", line 102, in <module>\r\n    _ansiballz_main()\r\n  File \"/var/tmp/ansible-tmp-1630082859.6666074-4612-147676839279342/AnsiballZ_step_ca_provisioner.py\", line 94, in _ansiballz_main\r\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n  File \"/var/tmp/ansible-tmp-1630082859.6666074-4612-147676839279342/AnsiballZ_step_ca_provisioner.py\", line 40, in invoke_module\r\n    runpy.run_module(mod_name='ansible_collections.maxhoesel.smallstep.plugins.modules.step_ca_provisioner', init_globals=None, run_name='__main__', alter_sys=True)\r\n  File \"/usr/lib64/python2.7/runpy.py\", line 170, in run_module\r\n    mod_name, loader, code, fname = _get_module_details(mod_name)\r\n  File \"/usr/lib64/python2.7/runpy.py\", line 113, in _get_module_details\r\n    code = loader.get_code(mod_name)\r\n  File \"/tmp/ansible_maxhoesel.smallstep.step_ca_provisioner_payload_ncWoUY/ansible_maxhoesel.smallstep.step_ca_provisioner_payload.zip/ansible_collections/maxhoesel/smallstep/plugins/modules/step_ca_provisioner.py\", line 263\r\n    module, result, {**args, **connection_run_args}\r\n                      ^\r\nSyntaxError: invalid syntax\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}
    - name: Add an ACME provisioner to CA
      maxhoesel.smallstep.step_ca_provisioner:
        name: ACME
        type: ACME
      become_user: step-ca
      notify: reload step-ca

Capture `step_cli` attempt to open tty and present better error result

Under Ubuntu 18.04 at least, the step_cli (e.g. for bootstrap) is attempting to open the TTY and issues the following error:

open /dev/tty: no such device or address

The module utils (in run.py) function run_step_cli_command doesn't capture this specific error, resulting in a much less useful error back to ansible:

Error running command 'step-cli ca bootstrap --ca-url= ...'

Trivial fix forthcoming to at least provide better feedback in Ansible.

This is also related to a feature request I posted to STEP CLI requesting a way to entirely avoid prompts, and associated attempted TTY access: smallstep/cli#502

feature: eliminate "expected error" on bootstrap

When bootstrapping a new host the first time, there will always be two "expected errors":

  1. checking if step-cli is installed:
TASK [maxhoesel.smallstep.step_bootstrap_host : Look for step_cli_executable] ****************************************************
fatal: [odysseus]: FAILED! => {"changed": false, "cmd": "step-cli version", "msg": "[Errno 2] No such file or directory: b'step-cli': b'step-cli'", "rc": 2}
...ignoring

TASK [maxhoesel.smallstep.step_cli : Download and extract step-cli archive] ******************************************************
changed: [odysseus]
  1. checking if the root cert is installed:
TASK [maxhoesel.smallstep.step_bootstrap_host : Check if cert is already installed] ******************************************
fatal: [odysseus]: FAILED! => {"changed": false, "cmd": ["step-cli", "certificate", "verify", "/etc/step/certs/root_ca.crt"], "delta": "0:00:00.062226", "end": "2021-07-21 12:46:09.637063", "msg": "non-zero return code", "rc": 1, "start": "2021-07-21 12:46:09.574837", "stderr": "failed to verify certificate: x509: certificate signed by unknown authority", "stderr_lines": ["failed to verify certificate: x509: certificate signed by unknown authority"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [maxhoesel.smallstep.step_bootstrap_host : Install CA cert into trust stores] *******************************************
changed: [odysseus]

While minor, and I know it's doing the right thing, it takes me by surprise and takes me a second to remember that these are "expected".

Any chance we could find a different way to do the check so that the error is not produced, but the end result is the same?

role naming choices

Is there a reason you chose the names of the roles to be ca_server and step_client ? To parallel the packages Smallstep provides, would the names not be better named step_ca and step_cli, respectively?

step_acme_cert: Certificate is not re-issued if subject/SAN changes

When adding a SAN in the role parameters after the certificate has already been issued, step_acme_cert still skips requesting a new cert. The issue is that we only check the cert for validity, but we don't assert that the contents are also correct.

Obvious workaround: Delete the old cert before running the role.

This is ultimately down to a deficiency in step_ca_certificate (see #188) which we already partially workaround here.
If the above issue gets resolves, we should simply be able to call the module with no further changes needed.

Lazy evaluation of certfile dicts in `step_acme_cert` fails if no path is specified

#82 introduced lazy evaluation for the step_acme_cert_certfile/keyfile variables so that users would not have to specify all dictionary keys whenever they wanted to change a single parameter. Unfortunately, this fails if the user supplies a dictionary without a path in it:

step_acme_cert_certfile:
  owner: max
  group: root

Result:

TASK [maxhoesel.smallstep.step_acme_cert : Look for existing certificate] *******************************************
fatal: [netbox-main.mngmt.maxhoesel.de]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'path'\n\nThe error appears to be in '/home/max/.ansible/collections/ansible_collections/maxhoesel/smallstep/roles/step_acme_cert/tasks/main.yml': line 5, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Look for existing certificate\n  ^ here\n"}

step_acme_cert needs the access the certificate path to check for its validity, but this fails if the user does not provide a path. To resolve this, I suggest that we explicitly combine the defaults and user-provided dicts in a task at the beginning of the role:

# tasks file for step_acme_cert
- include: check.yml

- name: Update cert/keyfile dicts with defaults
  set_fact:
    step_acme_cert_keyfile: "{{ step_acme_cert_keyfile_defaults | combine(step_acme_cert_keyfile) }}"
    step_acme_cert_certfile: "{{ step_acme_cert_certfile_defaults | combine(step_acme_cert_certfile) }}"

- name: Look for existing certificate
  stat:
    path: "{{ step_acme_cert_certfile.path }}"
  register: step_acme_cert_current_cert
....

module integration: Imrpove test verification

Many of our current integration tests simply run the module and assume that its output/action was valid. We should also check for their effects (such as certs on the filesystem) where possible

add role for setting up certificate renewal

Now that we (almost) have all the required modules, it would be cool to have a role that gets a cert from step-ca, installs it and then sets up a renewal job. This would basically have the same functionality as a certbot role, except with step-cli.

As for a name, step_certificate would work well imo.

Roles hardcoded /root/.step as STEPPATH for the root user

As discussed in #91, several roles in this collection hardcode the default STEPPATH of the root user to /root/step. This may cause problems if:

  • A user tries to run one of these roles as a non-root user, despite being not recommended.
  • The root users home dir is not located in /root

See the PR linked above for possible solutions. As of right now, none of these options are simple and/or reliable enough to be implemented, so this bug will remain unresolved for the time being.

Create step_ca_bootstrap module

The old step_client role automatically added certificates from user-supplied CAs to the system trust store using step ca bootstrap. It would make more sense to put this feature into a separate role wrapping around step ca bootstrap so that it can be used independently

step-cli executable naming

Upstream just changed the name of the step-cli executable from step to step-cli in the .deb package to prevent issues with the pre-existing step package on Debian-based systems. However, they have not changed the name for any of the .tar.gz archives

This leaves us with three options:

  • use step everywhere. this would involve renaming the execrable from the deb package or just using the RedHat install backend everywhere.
  • Account for both and use the different executable names on the respective distros.
  • Use step-cli everywhere by renaming the executable from the RedHat package and using the deb files for Debian hosts

Option 1 is out as it reintroduces the potential naming conflict. Option 2 would be neat, but also means more complexity for the roles (and especially modules, as we could need to have some autodetection for step-cli and step, but not the external step command. My choice therefore is Option 3, as it is easy to implement in the roles and modules (just provide a default value for the step_cli_executable but allow the user to override it).

Create step_ca role

The ca_server role, along with step_client was created in a rush and could really use a rewrite. To preserve compatibility and to follow upstream package names, this new role will be called step_ca.

step_ca role (and other step_ca_provisioner calls) break on step-cli >=0.20.0

step-cli 0.20.0 introduced changes to the step ca provisioner command, which affects how provisioners are removed (no type is required anymore). This is not yet compatible with the current module, and patching the module to make it compatible would introduce breaking changes for older step-cli versions (because they do expect the type parameter to be there).

I'm currently considering available options, but I think it would be best if this collection moved to a new versioning approach that follows the upstream step-cli tool. Each new upstream release would also be accompanied by a collection release, specifically tested to be compatible against that specific upstream release.

So for example, people running step-cli 0.20 would also install the collection version 0.20, while those running 0.21 would be using collection version 0.21, and so on. This approach means that this collection doesn't have to implement complicated backwards-compatibility behavior while still providing users with an easy and straightforward compatibility guarantee.

About molecule-docker+systemd issues and migrating Molecule tests to Podman...

As outlined here and here, newer releases of systemd don't play nice with systemd docker containers. This causes a "no such file or directory" error when running our molecule scenarios on newer distros (Fedora 34+/Archlinux).

For now, a simple workaround is to set the systemd.unified_cgroup_hierarchy kernel parameter to 0 on boot to force usage of cgroupv1.

It should be noted that the CI is currently unaffected, as it is still running on Ubuntu 20.04.
That said, we should probably look into a better long-term solution if the "systemd in docker" hack really is going away:

  • Option A: Use VMs, probably via Vagrant. I don't like this option because it would mean massively increased resource usage and test runtimes
  • Option B: Migrate a container runtime that supports systemd containers like Podman. This could be a good approach for the CI, but I'd like to preserve an option for running tests locally through the CT runtime that most of us already have installed - Docker.
  • Option C: Do nothing and hope that we get a new option for running systemd in docker. This would be my preferred option. Thankfully, this issue is not very urgent, so that's probably what I'll do for now.

root and intermediate password files not created

step-ca service doesn't start because the root and intermediate password files are missing.
Manually creating these files make the step-ca service start.
The role should ensure these files are present - but the files are not present and the role is skipped:

TASK [maxhoesel.smallstep.step_ca : Create root and intermediate password files] ***
skipping: [localca] => (item=None)
skipping: [localca] => (item=None)
skipping: [localca]

https://github.com/maxhoesel/ansible-collection-smallstep/blob/8c9143efd1aa22c63d693d4d92d475147cdbf5be/roles/step_ca/tasks/init.yml#L30-L42

Bug: `ValueError: No closing quotation` with `step_ca` role

I am attempting to use the step_ca role to initialize a ca server on a VM. When I attempt to run the role, I get an error that seems to amount to ValueError: No closing quotation. I've tried several different approaches, looked through the source in this repo, and also searched the internet for this error and I'm just not seeing anything. Apologies in advance if I am missing something obvious.

Here is my setup.

Ansible version: core 2.12.2
ansible_python: 3.8.10
Control node: macOS Monterey 12.2.1 (21D62)
Target node: Ubuntu 20.04 (see Vagrantfile below)
ansible_playbook_python: 3.10.2
maxhoesel.smallstep version: 0.4.9 (I've also attempted the HEAD of main in this repo; 08aa754)

Here is the Vagrantfile of the host on which I'm trying to install step ca:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"

  # VM definitions
  config.vm.define "certificate_authority" do |machine|
    machine.vm.hostname = "ca-local-dev.local"
  end
end

Here's the ansible play I'm using to install step_ca:

- name: Bootstrap certificate authority using `maxhoesel/smallstep`.
  hosts: ca
  roles:
    - maxhoesel.smallstep.step_ca
  vars:
    step_ca_name: "Homelab CA"
    step_ca_root_password: "an_extremely_secret_value"
  become:
    true

I am happy to post additional error output, but here is what seems to be the relevant part:

The full traceback is:
Traceback (most recent call last):
  File "/var/tmp/ansible-tmp-1646589610.442364-44741-275605781034203/AnsiballZ_command.py", line 107, in <module>
    _ansiballz_main()
  File "/var/tmp/ansible-tmp-1646589610.442364-44741-275605781034203/AnsiballZ_command.py", line 99, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/var/tmp/ansible-tmp-1646589610.442364-44741-275605781034203/AnsiballZ_command.py", line 47, in invoke_module
    runpy.run_module(mod_name='ansible.modules.command', init_globals=dict(_module_fqn='ansible.modules.command', _modlib_path=modlib_path),
  File "/usr/lib/python3.8/runpy.py", line 207, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib/python3.8/runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/tmp/ansible_ansible.legacy.command_payload_gvctg_67/ansible_ansible.legacy.command_payload.zip/ansible/modules/command.py", line 399, in <module>
  File "/tmp/ansible_ansible.legacy.command_payload_gvctg_67/ansible_ansible.legacy.command_payload.zip/ansible/modules/command.py", line 321, in main
  File "/usr/lib/python3.8/shlex.py", line 311, in split
    return list(lex)
  File "/usr/lib/python3.8/shlex.py", line 300, in __next__
    token = self.get_token()
  File "/usr/lib/python3.8/shlex.py", line 109, in get_token
    raw = self.read_token()
  File "/usr/lib/python3.8/shlex.py", line 191, in read_token
    raise ValueError("No closing quotation")
ValueError: No closing quotation
fatal: [certificate_authority]: FAILED! => {
    "changed": false,
    "module_stderr": "Shared connection to 127.0.0.1 closed.\r\n",
    "module_stdout": "Traceback (most recent call last):\r\n  File \"/var/tmp/ansible-tmp-1646589610.442364-44741-275605781034203/AnsiballZ_command.py\", line 107, in <module>\r\n    _ansiballz_main()\r\n  File \"/var/tmp/ansible-tmp-1646589610.442364-44741-275605781034203/AnsiballZ_command.py\", line 99, in _ansiballz_main\r\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n  File \"/var/tmp/ansible-tmp-1646589610.442364-44741-275605781034203/AnsiballZ_command.py\", line 47, in invoke_module\r\n    runpy.run_module(mod_name='ansible.modules.command', init_globals=dict(_module_fqn='ansible.modules.command', _modlib_path=modlib_path),\r\n  File \"/usr/lib/python3.8/runpy.py\", line 207, in run_module\r\n    return _run_module_code(code, init_globals, run_name, mod_spec)\r\n  File \"/usr/lib/python3.8/runpy.py\", line 97, in _run_module_code\r\n    _run_code(code, mod_globals, init_globals,\r\n  File \"/usr/lib/python3.8/runpy.py\", line 87, in _run_code\r\n    exec(code, run_globals)\r\n  File \"/tmp/ansible_ansible.legacy.command_payload_gvctg_67/ansible_ansible.legacy.command_payload.zip/ansible/modules/command.py\", line 399, in <module>\r\n  File \"/tmp/ansible_ansible.legacy.command_payload_gvctg_67/ansible_ansible.legacy.command_payload.zip/ansible/modules/command.py\", line 321, in main\r\n  File \"/usr/lib/python3.8/shlex.py\", line 311, in split\r\n    return list(lex)\r\n  File \"/usr/lib/python3.8/shlex.py\", line 300, in __next__\r\n    token = self.get_token()\r\n  File \"/usr/lib/python3.8/shlex.py\", line 109, in get_token\r\n    raw = self.read_token()\r\n  File \"/usr/lib/python3.8/shlex.py\", line 191, in read_token\r\n    raise ValueError(\"No closing quotation\")\r\nValueError: No closing quotation\r\n",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 1
}

I have also tried replacing the line in roles/step_ca/tasks/init.yml

command: "{{ step_cli_executable }} ca init {{ step_ca_init_args | join(' ') }}"

to

command: "{{ step_cli_executable }} ca init {{ step_ca_init_args | join(' ') | quote }}"

I don't get the error I posted above, but execution seems to just hang with that quote filter. Thanks in advance for any help.

Pass `force` option through `step_bootstrap_host` role to underlying module

I would like to pass a force option to the role to change the CA URL, or even replacing the CA with a different server entirely, and therefore a different fingerprint. However, the current implementation fo the step_bootstrap_host role does not have a flag to force the (re-)configuration of the client configuration or root CA certificate. Happily, the underlying step_ca_boostrap.py module does, so a simple fix is forthcoming.

modules: add `step_certificate_info` module

Sometimes a user may want to retrieve info about an existing certificate from a remote system.

step-cli provides the certificate inspect command for this purpose (which can output JSON), so it should be possible to implement a step_certificate_info module based on this command.

Related #188

Systemd does not run when using full path

OS: Fedora 36
systemd version: v250.3-8.fc36

Might be a missing selinux configuration, but this is the error that is produced

systemd[17323]: step-ca.service: Failed to locate executable /usr/bin/step-ca: Permission denied
systemd[17323]: step-ca.service: Failed at step EXEC spawning /usr/bin/step-ca: Permission denied
systemd[1]: step-ca.service: Main process exited, code=exited, status=203/EXEC
systemd[1]: step-ca.service: Failed with result 'exit-code'.

Solution: switch to using relative path. Alternative we can figure out deeper why this issue occurs. There are no file permission errors that I know of.

tagged version v. "release" changelog and version update skew

Max,

It seems a bit odd to me the process you are following to make a git tag for a version (e.g. v0.4.5) and then add a commit updating the CHANGELOG and version number in the galaxy.yml.

I noted this as we are using your github.com repository and tags rather than Galaxy in our requirements.yml, and therefore when we are getting the tagged version, the galaxy.yml and changelog are never "up to date".

Would it not be better to update the version in galaxy.yml and the CHANGELOG and tag that commit?

Use the new argument spec validation for roles

ansible-core 2.11 introduced a new argument spec method for roles that enables role parameter checks similar to those used in modules. Switching to these would allow us to remove our own parameter-checking tasks and probably catch a few more user errors.

According to the docs, it's possible to implement this argspec on roles while maintaining backwards-compatibility. That said, we'd still need to keep the old checks around for older ansible versions, at least while we're still supporting them. This might lead to the checks firing twice for newer versions, once from the argspec and once from our own tasks, but that should be preventable with a check against ansible_version

Testing Strategy

I'd like to have automated tests for all major components of the collection to prevent bugs or other issues from sneaking in. The current tests were (just like the initial two roles) written rather hastily and could use some restructuring, so now would be a good time for that.

  • The individual roles can easily be tested with molecule
  • I'm not sure what approach makes the most sense for modules and whether unit testing them makes sense in the context of this role. I barely have any experience testing modules, so I'll have to do some research on that
  • Integration tests could be done with molecule on a collection-wide level

add "lazy evaluation" of default values for cert and key path, owner, and mode

In the step_acme_cert role as currently implemented, the specification of the step_acme_cert_certfile and _keyfile variables require one to fully specify the entire collection of subkeys (path, owner, group, and mode), even if you want to override only a subset of those keys.

As a nice-to-have feature, one should have to specify only those keys and values that one wants to modify. E.g. instead of:

- name: step-client | test cert creation and renewal
  include_role:
    name: maxhoesel.smallstep.step_acme_cert
  vars:
    step_acme_cert_certfile:
      path: "{{ step_cert_directory }}/step-test-cert.crt"
      owner: root
      group: root
      mode: 644
    step_acme_cert_keyfile:
      path: "{{ step_cert_directory }}/step-test-cert.key"
      owner: root
      group: root
      mode: 600

... one can simplify to only:

- name: step-client | test cert creation and renewal
  include_role:
    name: maxhoesel.smallstep.step_acme_cert
  vars:
    step_acme_cert_certfile:
      path: "{{ step_cert_directory }}/step-test-cert.crt"
    step_acme_cert_keyfile:
      path: "{{ step_cert_directory }}/step-test-cert.key"

Naming scheme for modules

If we are to add more modules (and potentially roles) to this collection, we should have a clear and simple naming scheme for components to follow.

With roles, the only two we have right now are step_cli and step_ca - those just follow the upstream package names.

As for modules, this depends on their scope, but I'd suggest that we name them after their step command equivalent - so modules for generating certificates locally/from the CA cert would be named step_certificate and step_ca_certificate respectively.

Support remote provisioner management

Newer step versions support remote provisioner management using the admin parameters. This allows CA administrators to add and remove provisioners without needing direct access to the configuration. We should make this feature accessible to users of this collection, both in the modules and in the managed roles.

To support this feature, we need to do the following:

  • Add admin parameters to step_ca_provisioner module (Done in #142)
  • Add option to enable remote provisioning in step_ca role
  • Write tests for remote provisioner management
  • (Optional) Add a module for the step ca admin command to manage admin permissions
  • (Optional) Add parameters to step_ca that allow configuring admin users and permissions

`delegate_to: localhost` can break with `become: yes`

When I run the roles to install step CA on my infrastructure i'm hitting an error where it is telling me i'm using the wrong sudo password.

Here's the stack trace

TASK [maxhoesel.smallstep.step_cli : Get latest step-cli release information] *****************************************************************************************************************
fatal: [testnode1]: FAILED! => {
    "changed": false,
    "rc": 1
}

MSG:

MODULE FAILURE
See stdout/stderr for the exact error


MODULE_STDERR:


Sorry, try again.
[sudo via ansible, key=*****] password:
sudo: timed out reading password
sudo: 1 incorrect password attempt

The reason this is happening is i am running the role from my local machine. Since I have a different sudo password locally than what exists on the infrastructure, the task with delegate_to: localhost is failing.

Restructuring the module data

Instead of having all of the data flat like oidc_client_id, oidc_client_secret, how about restructuring it to a single object like oidc_provisioner or better step_ca_provisioners, and have all of the necessary checks done in the python script, e.g.:

module_args = dict(
  oidc_provisioner=dict(
    type='dict',
    options=dict(
      client_id=dict(type="str", required=True),
      client_secret=dict(type="str", no_log=True, required=True)
    )
  )
)

For the second option use the required_if and mutually_exclusive. I don't know how it will render in the documentation, but I guess it can be configured in the DOCUMENTATION section.

The issue here being that the documentation like in step_ca_provisioner_module is horrendous to follow.

Alias "STEP_RENEW" already defined

When numerous step renew systemd services are present, multiple sudoers files are installed with the same alias name. As a result, every command utilizing sudo will trigger a warning message:

/etc/sudoers.d/99_step-myservice-renew:1:90: Alias "STEP_RENEW" already defined
Cmnd_Alias  STEP_RENEW = /usr/bin/systemctl try-reload-or-restart myservice.service

Defining `null` defaults for variables makes checking more difficult

I sort of understand the reason to make sure that every variable has a default (defaults/main.yml), but in the case of defining variables with a null default, it makes checking for appropriate values more difficult. As it stands now, if you don't define a value for either stepca_root_password or stepca_intermediate_password, the check for validity will fail, not with your defined failure message, but with a true error (NoneType has no len()).

I would suggest commenting out the definitions in defaults/main.yml and adding revising the assert clauses, as in:

- name: Check that passwords are set
  assert:
    that:
      - stepca_root_password is defined
      - stepca_root_password | length > 0
      - stepca_intermediate_password is defined
      - stepca_intermediate_password | length > 0
    fail_msg: "Please make sure that both stepca_root_password and stepca_intermediate_password are set!"

This actually makes it easier to conditionalize use of the role in my standard usage as well, with a pre_task such as:

  pre_tasks:
    # Allow this playbook to be run on ALL hosts,
    # BUT, skip everything unless required vars are defined for this host.
    - name: step-ca | End pla when insufficient variables defined
      meta: end_host
      when: stepca_root_password is defined

"Cannot recover from stack overflow."

When applying this ansible role a python error occurs:

setup_smallstep (v0.4.7) was installed successfully
    localca: Running ansible-playbook...
Fatal Python error: Cannot recover from stack overflow.
Python runtime state: initialized

Current thread 0x00007fdbb2742740 (most recent call first):
  File "<frozen importlib._bootstrap>", line 174 in _get_module_lock
  File "<frozen importlib._bootstrap>", line 148 in __enter__
  File "<frozen importlib._bootstrap>", line 988 in _find_and_load
  File "<frozen importlib._bootstrap>", line 1014 in _gcd_import
  File "<frozen importlib._bootstrap>", line 219 in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 961 in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 991 in _find_and_load
  File "<frozen importlib._bootstrap>", line 1014 in _gcd_import
  File "<frozen importlib._bootstrap>", line 219 in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 961 in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 991 in _find_and_load
  File "<frozen importlib._bootstrap>", line 1014 in _gcd_import
  File "/usr/lib/python3.8/importlib/__init__.py", line 127 in import_module
  File "/usr/local/lib/python3.8/dist-packages/ansible/utils/collection_loader.py", line 512 in get_collection_role_path
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/definition.py", line 158 in _load_role_path
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/definition.py", line 94 in preprocess_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/base.py", line 222 in load_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/include.py", line 60 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/helpers.py", line 390 in load_list_of_roles
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/metadata.py", line 105 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/base.py", line 235 in load_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/metadata.py", line 59 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 223 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 224 in _load_role_data
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 172 in load
  File "/usr/local/lib/python3.8/dist-packages/ansible/playbook/role/__init__.py", line 308 in _load_dependencies
  ...
Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.

System is CentOS 7.x.

Migrate to major-based Scheme

A little over a year ago, this collection introduced a lockstep versioning scheme where the collection version and the step-cli version were required to match. For example, the current version of this collection (0.24.5) requires step-cli>=0.24,<0.25.

This versioning scheme was a convenient way for this collection to ensure compatibility with the smallstep tools, but it had some major negative consequences:

  • User flexibility: By requiring a specific version of step-clion every system, admins are forced to a specific step-cli version on all systems, and cannot upgrade to a newer version until this collection was updated
  • Collection flexibility: Any breaking changes in this collection could only be introduced with a major release of step-cli, forcing us to combine additions for new step-cli versions with otherwise unrelated breaking changes.
  • step-cli vs step-ca versions: By forcing a specific version of step-cli, the collection also indirectly affected the version of step-ca being run and implied guaranteed compatibility between the two. As far as I know there is no official compatibility guarantee from smallstep between different versions (I opened a discussion here), and I would argue that this is not a job this collection should be taking on anyway.

Most notably, this restriction just doesn't seem to be that necessary. step-cli 0.20 did introduce some breaking changes that affected the collection, but there have been no issues since then.

Finally, having an unstable collection version implies to users that the collection itself is changing rapidly - but this isn't really the case. Our module and roles have been reasonably stable and there is no reason for this to change soon.

All of these issues can be resolved by switching to an independent major versioning scheme, starting with collection version 1.0. Here is how this will work:

  • The next release of the collection will be version 1.0.0
  • Each major collection release will have a minimum supported step-cli version
    • This minimum version will be tested in CI, along with the latest step-cli release
  • Newer versions of step-cli are assumed to be compatible, until an issue is discovered
  • Any of the following will result in the release of a new major version
    • A breaking change in the collection content (modules, roles)
    • A breaking change in step-cli that is not possible to wrap around (such as the removal of a command)

Below is a list of the changes needed to introduce this versioning scheme in the collection:

  • Rework the CI so that different smallstep tool versions can be tested.
  • Update the collection docs to include compatible versions
  • Rework the module version detection mechanism to look for the minimum version
  • Decide on how to handle step-ca versions in the step_ca role - presumably step-ca will need a separate range of supported step-ca versions, along with its supported distros

Rewrite Collection Documentation and Tutorial

As part of the 1.x release, this collections docs could use an overhaul.

Right now, pretty much all of the general documentation is in the README.md, which makes the file long and difficult to read.
At the same time, many details aren't elaborated on and the step-by-step guide has not been updated in a while.

To bring the docs up to a standard deserving of a 1.x release, the following changes should be made:

  • Clean out the README - split out the step-by-step guide (preferably into a separate docs RST) and make the components overview more compact
  • Update the role docs to mention supported distributions explicitly, instead of saying "x and up"
  • Rewrite the step-by-step guide to be more thorough
    • Document the different approaches to certificate renewal and their pros/cons, specifically:
      • Using the step_ca_certificate module with renew (see #188)
      • Using the step_acme_cert role or its successor (see #127 and #339)
      • Using ACME on the CA with certbot, Caddy, etc
    • Include remote provisioner and admin usage once #141 gets resolved
  • Rewrite the module usage to be more clear about offline vs online usage

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

ansible-galaxy
galaxy.yml
  • community.general >=1.0.0
circleci
.circleci/config.yml
  • collection-testing 0.5.3
github-actions
.github/workflows/version-maintenance.yml
  • actions/checkout v4
  • release-drafter/release-drafter v6
  • actions/checkout v4
  • actions/setup-python v5
  • peter-evans/create-pull-request v6
  • ubuntu 22.04
pip_requirements
requirements.txt
  • ansible-lint ==24.5.0
  • pylint ==3.2.1
  • autopep8 ==2.1.0
  • pre-commit ==3.7.1
  • pytest ==8.2.0
  • pytest-virtualenv ==1.7.0
  • docker ==7.0.0
  • pyyaml ==6.0.1
  • packaging ==24.0
  • ansible-core ==2.16.6
  • antsibull-docs ==2.11.0
  • ansible-pygments ==0.1.1
  • sphinx ==7.3.7
  • sphinx-ansible-theme ==0.10.3
pre-commit
.pre-commit-config.yaml
  • hhatto/autopep8 v2.1.0
  • pre-commit/pre-commit-hooks v4.6.0
  • ansible/ansible-lint v24.5.0

  • Check this box to trigger a request for Renovate to run again on this repository

`step_ca_certificate`: add `state` parameter

Right now, the step_ca_certificate module simply calls step-cli without checking for any existing certificates.
This results in the following behavior:

  • If force is false: On the second run, step-cli asks for confirmation for overwriting the existing cert, causing the module to fail.
  • If force is true: The certificate is always overwritten, even when this is not needed.

Neither of these are really ideal from a user POV.
As a user, I would like the step_ca_certificate module to ensure that a valid certificate with the desired properties exists on the system, regardless of the underlying details.
Such a valid certificate must:

  • Be present at the given path
  • Have correct parameters, such as SANs
  • Be valid (not expired)

It should be step_ca_certificates job to ensure that these conditions are met, whether that includes creating a new certificate or not.


I propose the following approach to accomplish this:

  • Introduce the serial_number parameter and other parameters from step_ca_revoke
  • Introduce the revoke_on_delete parameter
  • Introduce a state parameter with the following options:
    • present
      - If the certificate doesn't exist, is expired or has a SAN parameter mismatch, create a new certificate
      - Else, do nothing
    • revoked
      • Use either the path or the serial number and ensure that the given certificate is revoked.
    • absent
      • Delete the certificate from the system
        • If revoke_on_delete is true, revoke the certificate first
  • Change the force parameter behavior: Instead of passing force on the command line to replace existing files (which we pretty much always want given the above model), use of force with present will now generate a new certificate on every single run.

Implementing this feature would require a few steps:

  • Implement checks for certificate validity:
    • Exists
    • Parameters correct
    • Valid
  • Implement functionality for:
    • Revoke
    • Delete
    • Force behavior change
  • Implementing tests to ensure correct behavior
  • Remove the step_ca_certificate_revoke module

Release workflow needs to be reworked

Now that the rest of the collection has been brought back up to a reasonable level of quality, it's time to address the release workflow.

Currently, I'm thinking of having two separate workflows, one for release preparation, and one for actually releasing on main.

Here's what we need to do before pushing to main:

  • Create a new release branch (this could be our trigger to start the workflow, just make sure that this branch is always named release/x.x.x)
  • Generate and commit the new changelog
  • Update galaxy.yml with the new release number
  • Create a PR from the release branch to main

Then, once that PR is approved, a second workflow can actually generate the release:

  • Create a github release with artifacts
  • Publish the collection on ansible-galaxy
  • Merge the changes back to devel

role to support JWK-based (x509) certificates

There is a role for creating X509 certificates via an ACME provisioner (step_acme_cert), but there is not one using other provisioners, e.g. JWK. The routines are mostly parallel, and the creation of the renewal via systemd should be similar if not identical. To date, I've been using the modules (step_ca_token and step_ca_certificate) directly, but I should rather be using a role that others can use, and vet.

I am starting development of a new role, and believe that the existing role could be subsumed by a single step_certificate role. But if you prefer, I could provide a separate step_jwk_certificate.

Preferences?

Permission error when ansible_user is unpriviliged

Hi,

thanks for this very useful collection, i am currently playing around with it on a few virtual machines, plain Debian 11.

I took your example ca.yml from the README.

- hosts: manager-1
  become: yes
  tasks:
    # Install and initialize the CA server.
    # There are a lot of configuration options, see the step_ca README for details
    - name: Install step-ca
      include_role:
        name: maxhoesel.smallstep.step_ca
      vars:
        step_ca_name: Example CA
        step_ca_root_password: "hunter2"
        step_ca_intermediate_password: "hunter2"
        step_ca_user: "step-ca"

    # The CA root cert fingerprint is used by clients to verify the authenticity of your CA.
    # You can save the output of this task and then pass it on to any client that you want to trust the CA.
    - name: Get root CA fingerprint
      command: 'step-cli certificate fingerprint /etc/step-ca/certs/root_ca.crt'
      register: root_ca_fp
    - name: Show root CA fingerprint
      debug:
        msg: "Fingerprint of root cert: {{ root_ca_fp.stdout }}"

My inventory contains

  vars:
    ansible_user: debian

as i connect as a non-priviliged user and then use become to elevate, root login is disabled.

But roles/step_ca/tasks/init.yml fails with

TASK [maxhoesel.smallstep.step_ca : Initialize CA] ********************************************************************************************************************************************************************************************************************************************
task path: /home/user/.ansible/collections/ansible_collections/maxhoesel/smallstep/roles/step_ca/tasks/init.yml:74
fatal: [manager-1]: FAILED! => {
    "msg": "Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chmod: invalid mode: ‘A+user:step-ca:rx:allow’\nTry 'chmod --help' for more information.\n}). For information on working around this, see https://docs.ansible.com/ansible-core/2.12/user_guide/become.html#risks-of-becoming-an-unprivileged-user"
}

The error points at https://docs.ansible.com/ansible-core/2.12/user_guide/become.html#risks-of-becoming-an-unprivileged-user

If i enable root login, and overwrite ansible_user: root the role runs fine and everything is set up correctly.

Could you take a look?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.