diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62d58da..59eb905 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -245,6 +245,18 @@ rpm-rockylinux-8-master-py3: {extends: '.test_instance'} # git-gentoo-stage3-systemd-3000-9-py3: {extends: '.test_instance'} # git-ubuntu-1804-3000-9-py2: {extends: '.test_instance'} # git-arch-base-latest-3000-9-py2: {extends: '.test_instance'} +pip-debian-10-tiamat-py3: {extends: '.test_instance'} +pip-ubuntu-2004-tiamat-py3: {extends: '.test_instance'} +pip-debian-11-master-py3: {extends: '.test_instance'} +pip-debian-10-master-py3: {extends: '.test_instance'} +pip-debian-9-master-py3: {extends: '.test_instance'} +pip-ubuntu-2004-master-py3: {extends: '.test_instance'} +pip-centos-8-master-py3: {extends: '.test_instance'} +pip-opensuse-leap-153-master-py3: {extends: '.test_instance'} +pip-opensuse-leap-152-master-py3: {extends: '.test_instance'} +pip-arch-base-latest-master-py3: {extends: '.test_instance'} +pip-gentoo-stage3-latest-master-py3: {extends: '.test_instance'} +pip-gentoo-stage3-systemd-master-py3: {extends: '.test_instance'} ############################################################################### # `release` stage: `semantic-release` diff --git a/.travis.yml b/.travis.yml index 2ccaba1..48afca8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -205,6 +205,18 @@ jobs: # - env: INSTANCE=git-gentoo-stage3-systemd-3000-9-py3 # - env: INSTANCE=git-ubuntu-1804-3000-9-py2 # - env: INSTANCE=git-arch-base-latest-3000-9-py2 + - env: INSTANCE=pip-debian-10-tiamat-py3 + - env: INSTANCE=pip-ubuntu-2004-tiamat-py3 + - env: INSTANCE=pip-debian-11-master-py3 + - env: INSTANCE=pip-debian-10-master-py3 + - env: INSTANCE=pip-debian-9-master-py3 + - env: INSTANCE=pip-ubuntu-2004-master-py3 + - env: INSTANCE=pip-centos-8-master-py3 + - env: INSTANCE=pip-opensuse-leap-153-master-py3 + - env: INSTANCE=pip-opensuse-leap-152-master-py3 + - env: INSTANCE=pip-arch-base-latest-master-py3 + - env: INSTANCE=pip-gentoo-stage-3-latest-master-py3 + - env: INSTANCE=pip-gentoo-stage-3-systemd-master-py3 ## Define the release stage that runs `semantic-release` - stage: 'release' diff --git a/docs/README.rst b/docs/README.rst index 2d77291..0edd062 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -55,15 +55,17 @@ Available states This is a shortcut for letsencrypt.install letsencrypt.config and letsencrypt.domains. -If `use_package` is `True` (the default), the formula will try to install the *certbot* package from your Distro's repo. +if `install_method` is `package` (the default), the formula will try to install the *certbot* package from your Distro's repo. Keep in mind that most distros don't have a package available by default: Ie, previous stable Debian (Stretch) requires a backports repo installed. Centos 7 requires EPEL, etc. This formula **DOES NOT** manage these repositories. Use the `apt-formula `_ or the `epel-formula `_ to manage them. -If `use_package` is `False` it installs and configures the letsencrypt cli from git, creates the requested certificates and installs renewal cron job. +If `install_method` is `git` it installs and configures the letsencrypt cli from git, creates the requested certificates and installs renewal cron job. + +If `install_method` is `pip` it installs and configures the letsencrypt cli from pip, creates the requested certificates and installs renewwal cron job. Allows plugin installation with `pip_pkgs`. ** WARNING ** -If you set `use_package` to `True`, it will: +If you set `install_method` to `package`, it will: * Delete all certbot's crons if they exist from a previous git-based installation (as the package uses a systemd's timer unit to renew all the certs) diff --git a/kitchen.yml b/kitchen.yml index b173de9..72535dd 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -461,6 +461,24 @@ suites: verifier: inspec_tests: - path: test/integration/git + - name: pip + provisioner: + state_top: + base: + '*': + - letsencrypt._mapdata + - letsencrypt.install + - letsencrypt.config + pillars: + top.sls: + base: + '*': + - letsencrypt + pillars_from_files: + letsencrypt.sls: test/salt/pillar/pip.sls + verifier: + inspec_tests: + - path: test/integration/pip - name: deb includes: - debian-11-tiamat-py3 diff --git a/letsencrypt/defaults.yaml b/letsencrypt/defaults.yaml index 6fe7e68..e2642b8 100644 --- a/letsencrypt/defaults.yaml +++ b/letsencrypt/defaults.yaml @@ -2,25 +2,30 @@ # vim: ft=yaml --- letsencrypt: - use_package: true + install_method: package pkgs: [] git_pkg: git service: certbot.timer - # Only used for the pkg install method (use_package: true), internal var + # Only used for the pkg install method (install_method = package), internal var _cli_path: /usr/bin/certbot - # Only used for the pkg install method (use_package: true), internal var + # Only used for the pkg install method (install_method = package), internal var _default_pkg: certbot - # Only used for the git install method (use_package: false) + # Only used for the git or pip install methods (install_method = (git|pip)) cli_install_dir: /opt/letsencrypt - # Only used for the git install method (use_package: false). If you want to - # have specific version of certbot you can enable it. The version value - # should match a certbot/certbot branch - # version: 0.30.x + # Only used for the git or pip install methods (install_method = (git|pip)). + # If you want to have specific version of certbot you can enable it. The + # version value should match a certbot/certbot branch version: 0.30.x config_dir: path: /etc/letsencrypt user: root group: root mode: 755 + # Only used for the pip install method (install_method = pip). Can be used to + # install plugins for certbot. + pip_pkgs: [] + # Only used for the pip install method (install_method = pip), internal var + virtualenv_pkg: + - python3-virtualenv config: server: https://acme-v02.api.letsencrypt.org/directory agree-tos: true diff --git a/letsencrypt/domains.sls b/letsencrypt/domains.sls index 50c55e2..2e552c7 100644 --- a/letsencrypt/domains.sls +++ b/letsencrypt/domains.sls @@ -3,7 +3,7 @@ {% from "letsencrypt/map.jinja" import letsencrypt with context %} -{% if letsencrypt.use_package %} +{% if letsencrypt.install_method == 'package' %} {% set check_cert_cmd = letsencrypt._cli_path ~ ' certificates --cert-name' %} {% set renew_cert_cmd = letsencrypt._cli_path ~ ' renew' %} {% set create_cert_cmd = letsencrypt._cli_path %} @@ -15,7 +15,11 @@ {% else %} {% set check_cert_cmd = '/usr/local/bin/check_letsencrypt_cert.sh' %} {% set renew_cert_cmd = '/usr/local/bin/renew_letsencrypt_cert.sh' %} - {% set create_cert_cmd = letsencrypt.cli_install_dir ~ '/letsencrypt-auto' %} + {% if letsencrypt.install_method == 'pip' %} + {% set create_cert_cmd = letsencrypt.cli_install_dir ~ '/bin/certbot' %} + {% else %} + {% set create_cert_cmd = letsencrypt.cli_install_dir ~ '/letsencrypt-auto' %} + {% endif %} {% set old_check_cert_cmd_state = 'managed' %} {% set old_renew_cert_cmd_state = 'managed' %} @@ -63,11 +67,11 @@ create-initial-cert-{{ setname }}-{{ domainlist | join('+') }}: {{ installer }} \ --cert-name {{ setname }} \ -d {{ domainlist|join(' -d ') }} - {% if not letsencrypt.use_package %} + {% if letsencrypt.install_method != 'package' %} - cwd: {{ letsencrypt.cli_install_dir }} {% endif %} - unless: - {% if letsencrypt.use_package %} + {% if letsencrypt.install_method == 'package' %} - fun: cmd.run python_shell: true cmd: | @@ -78,7 +82,7 @@ create-initial-cert-{{ setname }}-{{ domainlist | join('+') }}: - {{ check_cert_cmd }} {{ setname }} {{ domainlist | join(' ') }} {% endif %} - require: - {% if letsencrypt.use_package %} + {% if letsencrypt.install_method == 'package' %} - pkg: letsencrypt-client {% else %} - file: {{ check_cert_cmd }} @@ -95,7 +99,7 @@ letsencrypt-crontab-{{ setname }}-{{ domainlist[0] }}: - identifier: letsencrypt-{{ setname }}-{{ domainlist[0] }} - require: - cmd: create-initial-cert-{{ setname }}-{{ domainlist | join('+') }} - {% if letsencrypt.use_package %} + {% if letsencrypt.install_method == 'package' %} - pkg: letsencrypt-client {% else %} - file: {{ renew_cert_cmd }} diff --git a/letsencrypt/files/renew_letsencrypt_cert.sh.jinja b/letsencrypt/files/renew_letsencrypt_cert.sh.jinja index 832237f..34429df 100644 --- a/letsencrypt/files/renew_letsencrypt_cert.sh.jinja +++ b/letsencrypt/files/renew_letsencrypt_cert.sh.jinja @@ -2,7 +2,11 @@ {% from "letsencrypt/map.jinja" import letsencrypt with context %} COMMON_NAME="$1" +{% if letsencrypt.install_method == 'pip' %} +{{ letsencrypt.cli_install_dir }}/bin/certbot renew --non-interactive || exit 1 +{% else %} {{ letsencrypt.cli_install_dir }}/letsencrypt-auto renew --non-interactive || exit 1 +{% endif %} cat {{ letsencrypt.config_dir.path }}/live/${COMMON_NAME}/fullchain.pem \ {{ letsencrypt.config_dir.path }}/live/${COMMON_NAME}/privkey.pem \ > {{ letsencrypt.config_dir.path }}/live/${COMMON_NAME}/fullchain-privkey.pem || exit 1 diff --git a/letsencrypt/install.sls b/letsencrypt/install.sls index bacad1b..8a643de 100644 --- a/letsencrypt/install.sls +++ b/letsencrypt/install.sls @@ -3,7 +3,7 @@ {%- from "letsencrypt/map.jinja" import letsencrypt with context %} {#- Use empty default for `grains.osfinger`, which isn't available in all distros #} -{%- if letsencrypt.use_package and +{%- if letsencrypt.install_method == 'package' and grains.osfinger|d('') == 'Amazon Linux-2' %} {%- set rhel_ver = '7' %} letsencrypt_external_repo: @@ -19,12 +19,21 @@ letsencrypt_external_repo: - pkg: letsencrypt-client {%- endif %} +{%- if letsencrypt.install_method == 'package' and grains.os|d('') == 'CentOS' %} +letsencrypt-epel-releasehttps://github.com/XeryusTC/letsencrypt-formula.git: + pkg.installed: + - pkgs: + - epel-release + - require_in: + - pkg: letsencrypt-client +{%- endif %} + letsencrypt-client: - {%- if letsencrypt.use_package %} + {%- if letsencrypt.install_method == 'package' %} {%- set pkgs = letsencrypt.pkgs or [letsencrypt._default_pkg] %} pkg.installed: - pkgs: {{ pkgs | json }} - {%- else %} + {%- elif letsencrypt.install_method == 'git' %} pkg.installed: - name: {{ letsencrypt.git_pkg }} {%- if letsencrypt.version is defined and letsencrypt.version|length %} @@ -38,5 +47,20 @@ letsencrypt-client: - target: {{ letsencrypt.cli_install_dir }} - force_reset: True {%- endif %} + {%- elif letsencrypt.install_method == 'pip' %} + pkg.installed: + - pkgs: {{ letsencrypt.virtualenv_pkg | json }} + virtualenv.managed: + - name: {{ letsencrypt.cli_install_dir }} + - python: python3 + - pip_pkgs: + {%- if letsencrypt.version is defined and letsencrypt.version|length %} + - certbot=={{ letsencrypt.version }} + {%- else %} + - certbot + {%- endif %} + {%- for pkg in letsencrypt.pip_pkgs %} + - {{ pkg }} + {%- endfor %} {%- endif %} - reload_modules: True diff --git a/letsencrypt/map.jinja b/letsencrypt/map.jinja index 5e6d24e..05ece86 100644 --- a/letsencrypt/map.jinja +++ b/letsencrypt/map.jinja @@ -18,9 +18,18 @@ merge=salt['grains.filter_by']( osfingermap, grain='osfinger', - merge = salt['pillar.get']('letsencrypt', {}), + merge=salt['config.get']('letsencrypt', merge='recurse'), ), ), ), base='letsencrypt') %} + +{# Make backwards compatible with use_package #} +{% if letsencrypt.use_package is defined %} + {% if letsencrypt.use_package %} + {{ letsencrypt | set_dict_key_value('install_method', 'package') }} + {% else %} + {{ letsencrypt | set_dict_key_value('install_method', 'git') }} + {% endif %} +{% endif %} diff --git a/letsencrypt/osfamilymap.yaml b/letsencrypt/osfamilymap.yaml index 5222e90..3c1ede8 100644 --- a/letsencrypt/osfamilymap.yaml +++ b/letsencrypt/osfamilymap.yaml @@ -4,7 +4,15 @@ RedHat: service: certbot-renew.timer FreeBSD: - # Only used for the pkg install method (use_package: true), internal var + # Only used for the pkg install method (install_method: package), internal var _cli_path: /usr/local/bin/certbot Gentoo: git_pkg: dev-vcs/git + virtualenv_pkg: + - dev-python/virtualenv +Debian: + virtualenv_pkg: + - virtualenv +Arch: + virtualenv_pkg: + - python-virtualenv diff --git a/letsencrypt/service.sls b/letsencrypt/service.sls index be30a46..2ae7f38 100644 --- a/letsencrypt/service.sls +++ b/letsencrypt/service.sls @@ -3,7 +3,7 @@ {% from "letsencrypt/map.jinja" import letsencrypt with context %} -{% if letsencrypt.use_package %} +{% if letsencrypt.install_method == 'package' %} letsencrypt-service-timer: service.running: - name: {{ letsencrypt.service }} diff --git a/pillar.example b/pillar.example index 72460f9..b3a9e3d 100644 --- a/pillar.example +++ b/pillar.example @@ -2,8 +2,8 @@ # vim: ft=yaml --- letsencrypt: - # Install using packages instead of git - use_package: true + # Install using package, git or pip + install_method: package # A list of package/s to install. To find the correct name for the variant # you want to use, check https://certbot.eff.org/all-instructions # Usually, you'll need a single one, but you can also add other plugins here. @@ -13,12 +13,17 @@ letsencrypt: - python3-certbot-apache # - python3-certbot-nginx # - python3-dns-route53 - # Only used for the git install method (use_package: false) + # Only used for the git or pip install methods (install_method = (git|pip)) cli_install_dir: /opt/letsencrypt - # Only used for the git install method (use_package: false). If you want to - # have specific version of certbot you can enable it. The version value - # should match a certbot/certbot branch. + # Only used for the git or pip install methods (install_method = git|pip)). + # If you want to have specific version of certbot you can enable it. The + # version value should match a certbot/certbot branch. version: 0.30.x + # Only used for the pip install method (install_method = pip). Can be used to + # install plugins for certbot. Default: [] + pip_pkgs: + - certbot-dns-azure + - certbot-dns-powerdns # Subcommand used for certificates' first generation cmd ( run | certonly | renew ) create_init_cert_subcmd: certonly # Any parameter from the cli can be specified in the config file diff --git a/test/integration/pip/README.md b/test/integration/pip/README.md new file mode 100644 index 0000000..282a4c8 --- /dev/null +++ b/test/integration/pip/README.md @@ -0,0 +1,50 @@ +# InSpec Profile: `git` + +This shows the implementation of the `pip` InSpec [profile](https://github.com/inspec/inspec/blob/master/docs/profiles.md). + +## Verify a profile + +InSpec ships with built-in features to verify a profile structure. + +```bash +$ inspec check git +Summary +------- +Location: git +Profile: profile +Controls: 4 +Timestamp: 2019-06-24T23:09:01+00:00 +Valid: true + +Errors +------ + +Warnings +-------- +``` + +## Execute a profile + +To run all **supported** controls on a local machine use `inspec exec /path/to/profile`. + +```bash +$ inspec exec git +.. + +Finished in 0.0025 seconds (files took 0.12449 seconds to load) +8 examples, 0 failures +``` + +## Execute a specific control from a profile + +To run one control from the profile use `inspec exec /path/to/profile --controls name`. + +```bash +$ inspec exec git --controls package +. + +Finished in 0.0025 seconds (files took 0.12449 seconds to load) +1 examples, 0 failures +``` + +See an [example control here](https://github.com/inspec/inspec/blob/master/examples/profile/controls/example.rb). diff --git a/test/integration/pip/controls/letsencrypt_spec.rb b/test/integration/pip/controls/letsencrypt_spec.rb new file mode 100644 index 0000000..47db24f --- /dev/null +++ b/test/integration/pip/controls/letsencrypt_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +describe file('/opt/letsencrypt') do + it { should be_directory } + it { should be_owned_by 'root' } + it { should be_grouped_into 'root' } + it { should be_readable } + its('size') { should be > 25 } +end + +describe file('/etc/letsencrypt/cli.ini') do + it { should be_file } + it { should be_owned_by 'root' } + it { should be_grouped_into 'root' } + it { should be_readable } + its('size') { should be > 1 } + its('content') do + should match 'server = https://acme-staging.api.letsencrypt.org/directory' + end + its('content') { should match 'authenticator = standalone' } + its('content') { should match 'File managed by Salt' } +end + +describe command('/opt/letsencrypt/bin/certbot plugins') do + its('stdout') { should match(/dns-powerdns/) } +end diff --git a/test/integration/pip/inspec.yml b/test/integration/pip/inspec.yml new file mode 100644 index 0000000..11f6cfc --- /dev/null +++ b/test/integration/pip/inspec.yml @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +name: pip +title: letsencrypt formula +maintainer: SaltStack Formulas +license: Apache-2.0 +# yamllint disable-line rule:line-length +summary: Verify that the letsencrypt formula is setup and configured correctly using `pip` +depends: + - name: share + path: test/integration/share +supports: + - platform-name: debian + - platform-name: ubuntu + - platform-name: centos + - platform-name: fedora + - platform-name: opensuse + - platform-name: suse + - platform-name: freebsd + - platform-name: openbsd + - platform-name: amazon + - platform-name: oracle + - platform-name: arch + - platform-name: gentoo + - platform-name: almalinux + - platform-name: rocky + - platform: windows diff --git a/test/salt/pillar/deb.sls b/test/salt/pillar/deb.sls index eeb2215..19e4416 100644 --- a/test/salt/pillar/deb.sls +++ b/test/salt/pillar/deb.sls @@ -2,7 +2,7 @@ # vim: ft=yaml --- letsencrypt: - use_package: true + install_method: package config: | server = https://acme-staging.api.letsencrypt.org/directory email = saltstack-letsencrypt-formula@example.com diff --git a/test/salt/pillar/git.sls b/test/salt/pillar/git.sls index b4e7a8d..d1a6c29 100644 --- a/test/salt/pillar/git.sls +++ b/test/salt/pillar/git.sls @@ -2,7 +2,7 @@ # vim: ft=yaml --- letsencrypt: - use_package: false + install_method: git version: 0.26.x config: | server = https://acme-staging.api.letsencrypt.org/directory diff --git a/test/salt/pillar/pip.sls b/test/salt/pillar/pip.sls new file mode 100644 index 0000000..da4f551 --- /dev/null +++ b/test/salt/pillar/pip.sls @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +letsencrypt: + install_method: pip + version: 1.7.0 + pip_pkgs: + - certbot-dns-powerdns + config: | + server = https://acme-staging.api.letsencrypt.org/directory + email = saltstack-letsencrypt-formula@example.com + authenticator = standalone + agree-tos = True + renew-by-default = True + domainsets: + www: + - letsencrypt-formula.example.com diff --git a/test/salt/pillar/rpm.sls b/test/salt/pillar/rpm.sls index 5865b85..bb1cd91 100644 --- a/test/salt/pillar/rpm.sls +++ b/test/salt/pillar/rpm.sls @@ -2,7 +2,7 @@ # vim: ft=yaml --- letsencrypt: - use_package: true + install_method: package config: server: https://acme-staging.api.letsencrypt.org/directory email: saltstack-letsencrypt-formula@example.com