diff --git a/docs/README.rst b/docs/README.rst index 530d4b3..9b9f741 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -71,6 +71,11 @@ Installs the rspamd package. Configures the rspamd package. +``rspamd.dkim_keys`` +---------------- + +Creates and configures the DKIM signing keys for rspamd. See the ``pillar.example`` for examples of how to use it. + ``rspamd.service`` ----------------- diff --git a/pillar.example b/pillar.example index 41430d7..7c357a3 100644 --- a/pillar.example +++ b/pillar.example @@ -2,6 +2,11 @@ # vim: ft=yaml --- rspamd: + # These are the user and group that will be used for the dkim files + # (both default to `_rspamd`, as set in both Debian and Centos) + # user: _rspamd + # group: _rspamd + use_upstream_repo: true # If you want to install the redis-server using this formula, set this # to true. It will install the redis-server package for the supported distros @@ -10,6 +15,20 @@ rspamd: # Default: false manage_redis: true + # Allows you to manage dkim keys through rspamadm. See examples below + # See https://www.rspamd.com/doc/modules/dkim_signing.html#dkim-key-management + # Default: false + manage_dkim_keys: true + + # Where dkim keys will be stored + dkim_keys_dir: /var/lib/rspamd/dkim + + # If true, all files under the not managed by the formula + # will be removed. Use with caution + # Default: false. All existing files will be kept + # clean_dkim_keys_dir: false + + ## RSPAMD config config: # rspamd uses two type of config files to modify the configuration, # determined by the file extension: @@ -96,21 +115,23 @@ rspamd: write_servers: localhost read_servers: localhost - dkim: + dkim_signing: module: true # File 'ext' will be set to 'conf' + # These parameters here are merged and/or overwritten by those + # defined in the `rspamd:dkim_keys` dict (see below) domain: - example.com: + this.should.be.merged.too: selector: dkim - privkey: /var/lib/rspamd/dkim/example.com.dkim.key + path: /var/lib/rspamd/dkim/should.be.merged.with.the.others.key example.net: - selector: dkim - privkey: /var/lib/rspamd/dkim/example.net.dkim.key - nom: 1234 - stri: cadena1 - lis: - - a - - b - - c + selector: fancy_selector + path: /var/lib/rspamd/dkim/this.will.be.overrriden + nom: 1234 + stri: cadena1 + lis: + - a + - b + - c override: rspamd: @@ -123,7 +144,47 @@ rspamd: # yamllint enable rule:line-length options: {} - logging: {} + logging: + some: parameter worker-normal: {} worker-controller: {} worker-proxy: {} + + ## DKIM keys management + # All domains and keys added in this dict and enabled (the default) will be + # ADDED to the `config:local:dkim_signing:domain` dict above. For any given domain, + # values set in this dict will OVERRIDE those set in the dict above if the same + # key exist in both places. + # Keys created here can be disabled (ie, not added to the config file) just + # setting `ehable: false` to it. + # Keys not listed here will be REMOVED from disk if `clean_dkim_keys_diri: true` + dkim_keys: + identifier1: + # If key should be used in the config generated by the rspamd.config state + # Default: true + # enable: true + + domain: example.net # required. If not set, the identifier will be used + + # Selector used to build the selector, ie ._domainkey + # Defaults to `domain` above + # selector: something + + # File where the privkey will be stored + # Defaults to /.key + # privkey_file: + + # File where the txt output will be stored + # Defaults to /.txt + # txt_file: + + # bits: # key size. Defaults to 2048 + # type: # Either 'rsa' or 'ed25519'. Defaults to 'rsa' + + identifier2: # Will be managed and enabled in the config + domain: example.com + type: ed25519 + identifier3: # Will be managed but disabled in the config + enable: false + domain: example.org + just.a.domain: {} # This will be enabled with all the defaults diff --git a/rspamd/config.sls b/rspamd/config.sls index c01306e..04fb9fc 100644 --- a/rspamd/config.sls +++ b/rspamd/config.sls @@ -12,6 +12,33 @@ {%- set filename = rspamd.base_dir ~ '/' ~ type ~ '.d/' ~ file ~ '.' ~ ext %} {%- endif %} + {# If `manage_dkim_keys: true` #} + {%- if file == 'dkim_signing' and rspamd.manage_dkim_keys %} + + {# add a domain entry in the dkim_signing dict if it does not exist #} + {%- do params.update({'domain': {} }) if params.domain is not defined %} + + {%- for dom, domp in rspamd.dkim_keys.items() %} + {%- set domain = domp.domain | default(dom) %} + + {# enable is default for domains, so if the parameter is undefined, + it means we consider it true #} + {%- if domp.enable | default(true) %} + {%- set privkey_file = domp.privkey_file | default( rspamd.dkim_keys_dir ~ '/' ~ domain ~ '.key') %} + {%- set selector = domp.selector | default(domain) %} + + {# add the domain to the domains dict if it does not exist #} + {%- do params.domain.update({domain: {} }) if params.domain[domain] is not defined %} + + {# update the selector and file for the domain #} + {%- do params.domain[domain].update({'path': privkey_file }) %} + {%- do params.domain[domain].update({'selector': selector}) if params.domain[domain]['selector'] is not defined %} + {%- else %} + {%- do params.domain.pop(domain) if params.domain[domain] is defined %} + {%- endif %} + {%- endfor %} + {%- endif %} + {%- set enabled = false if (params == {} or (params.enable is defined and params.enable == false)) else true %} {{ filename }}: {%- if enabled %} diff --git a/rspamd/defaults.yaml b/rspamd/defaults.yaml index 4af5268..bd8cfcd 100644 --- a/rspamd/defaults.yaml +++ b/rspamd/defaults.yaml @@ -6,10 +6,18 @@ rspamd: repo: humanname: Rspamd Official Repository + user: _rspamd + group: _rspamd + manage_redis: false redis_pkg: redis redis_service: redis + manage_dkim_keys: false + dkim_keys_dir: /var/lib/rspamd/dkim + clean_dkim_keys_dir: false + dkim_keys: {} + pkg: rspamd base_dir: /etc/rspamd diff --git a/rspamd/dkim_keys.sls b/rspamd/dkim_keys.sls new file mode 100644 index 0000000..3565241 --- /dev/null +++ b/rspamd/dkim_keys.sls @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# vim: ft=sls + +{% from "rspamd/map.jinja" import rspamd with context %} + +{%- if rspamd.manage_dkim_keys -%} +rspamd_dkim_keys_dir: + file.directory: + - name: {{ rspamd.dkim_keys_dir }} + - user: {{ rspamd.user }} + - group: {{ rspamd.group }} + - mode: 755 + - clean: {{ rspamd.clean_dkim_keys_dir }} + - require: + - pkg: rspamd_pkg + + {%- for key, params in rspamd.dkim_keys.items() %} + {%- set domain = params.domain | default(key) %} + {%- set selector = params.selector | default(domain) %} + {%- set privkey_file = params.privkey_file | default( rspamd.dkim_keys_dir ~ '/' ~ domain ~ '.key') %} + {%- set txt_file = params.txt_file | default( rspamd.dkim_keys_dir ~ '/' ~ domain ~ '.txt') %} + {%- set bits = params.bits | default(2048) %} + {%- set type = params.type | default('rsa') %} +rspamd_dkim_keys_{{ key }}: + cmd.run: + - name: | + mkdir -p {{ rspamd.dkim_keys_dir }} && \ + rspamadm dkim_keygen \ + --selector '{{ selector }}' \ + --bits {{ bits }} \ + --domain {{ domain }} \ + --privkey {{ privkey_file }} \ + > {{ txt_file }} + - unless: test -f {{ txt_file }} + - require_in: + - file: rspamd_dkim_keys_{{ privkey_file }}_perms + - file: rspamd_dkim_keys_{{ txt_file }}_perms + - sls: config + - service: rspamd_service + +rspamd_dkim_keys_{{ privkey_file }}_perms: + file.managed: + - name: {{ privkey_file }} + - user: {{ rspamd.user }} + - group: {{ rspamd.group }} + - mode: 640 + - onlyif: test -f {{ privkey_file }} + - require_in: + - file: rspamd_dkim_keys_dir + +rspamd_dkim_keys_{{ txt_file }}_perms: + file.managed: + - name: {{ txt_file }} + - user: {{ rspamd.user }} + - group: {{ rspamd.group }} + - mode: 640 + - onlyif: test -f {{ txt_file }} + - require_in: + - file: rspamd_dkim_keys_dir + + {%- endfor %} +{%- endif %} diff --git a/rspamd/init.sls b/rspamd/init.sls index 5a3145a..0ff56b9 100644 --- a/rspamd/init.sls +++ b/rspamd/init.sls @@ -3,5 +3,6 @@ include: - .install + - .dkim_keys - .config - .service diff --git a/rspamd/osfamilymap.yaml b/rspamd/osfamilymap.yaml index 78f212d..abd31bc 100644 --- a/rspamd/osfamilymap.yaml +++ b/rspamd/osfamilymap.yaml @@ -17,7 +17,7 @@ RedHat: file: /etc/yum.repos.d/rspamd.repo key_url: https://rspamd.com/rpm-stable/gpg.key # yamllint disable rule:line-length - baseurl: http://rspamd.com/rpm-stable/{{ grains['os']|lower }}-{{ grains['osmajorrelease']}}/{{ grains['osarch'] }}/ + baseurl: http://rspamd.com/rpm-stable/{{ grains['os']|lower }}-{{ grains['osmajorrelease'] }}/{{ grains['osarch'] }}/ # yamllint enable rule:line-length gpgcheck: 1 gpgkey: http://rspamd.com/rpm/gpg.key @@ -25,3 +25,5 @@ RedHat: Arch: use_upstream_repo: false + user: root + group: root diff --git a/test/integration/default/controls/config.rb b/test/integration/default/controls/config.rb new file mode 100644 index 0000000..723f9af --- /dev/null +++ b/test/integration/default/controls/config.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +config_dir = '/etc/rspamd' + +rspamd_conf_override = <<~RSPAMD_CONF_OVERRIDE + options { + .include "$CONFDIR/options.inc"; + .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"; + .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"; + pidfile = "$RUNDIR/rspamd.pid"; + } +RSPAMD_CONF_OVERRIDE + +dkim_domains = <<~DKIM_DOMAINS + domain { + example.com { + path = "/var/lib/rspamd/dkim/example.com.key"; + selector = "example.com"; + } + example.net { + path = "/var/lib/rspamd/dkim/example.net.key"; + selector = "fancy_selector"; + } + just.a.domain { + path = "/var/lib/rspamd/dkim/just.a.domain.key"; + selector = "just.a.domain"; + } + this.should.be.merged.too { + path = "/var/lib/rspamd/dkim/should.be.merged.with.the.others.key"; + selector = "dkim"; + } + } +DKIM_DOMAINS + +control 'rspamd configuration' do + impact 1.0 + title 'Manage the Rspamd Configuration' + desc ' + Manage the rspamd configuration + ' + tag 'rspamd', 'config' + + %w[ + local.d + override.d + ].each do |d| + describe file("#{config_dir}/#{d}") do + it { should be_directory } + it { should be_owned_by 'root' } + it { should be_grouped_into 'root' } + its('mode') { should cmp '0755' } + end + end + + describe file("#{config_dir}/rspamd.conf.override") do + its('content') { should include(rspamd_conf_override) } + end + + describe file("#{config_dir}/local.d/redis.conf") do + its('content') { should match(/^read_servers = "localhost";/) } + its('content') { should match(/^write_servers = "localhost";/) } + end + + describe file("#{config_dir}/local.d/dkim_signing.conf") do + its('content') { should match(/^lis = \["a", "b", "c"\];/) } + its('content') { should match(/^nom = 1234;/) } + its('content') { should match(/^stri = "cadena1";/) } + its('content') { should include(dkim_domains) } + end +end diff --git a/test/integration/default/controls/file.rb b/test/integration/default/controls/file.rb new file mode 100644 index 0000000..0e217c3 --- /dev/null +++ b/test/integration/default/controls/file.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +case platform[:family] +when 'linux', 'arch' + rspamd_user = rspamd_group = 'root' +else + rspamd_user = rspamd_group = '_rspamd' +end + +control 'rspamd dkim_keys' do + impact 1.0 + title 'Manage the DKIM key files and directory' + desc ' + Manage the DKIM keys and directory + ' + tag 'rspamd', 'dkim_keys' + + describe file('/var/lib/rspamd/dkim') do + it { should be_directory } + it { should be_owned_by rspamd_user } + it { should be_grouped_into rspamd_group } + its('mode') { should cmp '0755' } + end + + %w[ + example.com.key + example.com.txt + example.net.key + example.net.txt + example.org.key + example.org.txt + just.a.domain.key + just.a.domain.txt + ].each do |f| + describe file("/var/lib/rspamd/dkim/#{f}") do + it { should be_file } + it { should be_owned_by rspamd_user } + it { should be_grouped_into rspamd_group } + its('mode') { should cmp '0640' } + end + end +end diff --git a/test/integration/default/controls/service.rb b/test/integration/default/controls/service.rb index 4d19ad2..f2ce269 100644 --- a/test/integration/default/controls/service.rb +++ b/test/integration/default/controls/service.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -control 'rspamd service' do - impact 1.0 - title 'Manage the Rspamd Service' +control 'redis service' do + impact 0.5 + title 'Manage the Redis Service' desc ' - Manage the rspamd service + Manage the redis service ' - tag 'rspamd', 'service' + tag 'redis', 'service' redis_service = case os[:family] when 'debian' @@ -19,34 +19,39 @@ it { should be_enabled } it { should be_running } end +end + +control 'rspamd service' do + impact 1.0 + title 'Manage the Rspamd Service' + desc ' + Manage the rspamd service + ' + tag 'rspamd', 'service' describe service('rspamd') do it { should be_enabled } it { should be_running } end - # We seem to be hiting this - if os[:family] == 'debian' - %w[ - 11332 - 11333 - 11334 - ].each do |pn| - describe port(pn) do - # It takes a while to the port to be listening, - # se we add a little sleep - before do - 30.times do - unless port(pn).listening? - puts "Port #{pn} isn't ready, retrying.." - sleep 1 - end + %w[ + 11332 + 11333 + 11334 + ].each do |pn| + describe port(pn) do + # It takes a while to the port to be listening, so we add a little sleep + before do + 30.times do + unless port(pn).listening? + puts "Port #{pn} isn't ready, retrying.." + sleep 1 end end - it { should be_listening } - its('protocols') { should include 'tcp' } - its('addresses') { should include '127.0.0.1' } end + it { should be_listening } + its('protocols') { should include 'tcp' } + its('addresses') { should include '127.0.0.1' } end end end