Manage /etc/{at,cron}.{allow,deny} files
Related-Prod: PROD-22546
Related-Prod: PROD-22664
Change-Id: I66a35ef3d2436541ef70f02e2631fa8d4d86e5e9
diff --git a/README.rst b/README.rst
index 986ec72..50e1b13 100644
--- a/README.rst
+++ b/README.rst
@@ -235,7 +235,54 @@
automatic_reboot: true
automatic_reboot_time: "02:00"
-Linux with cron jobs
+Managing cron tasks
+-------------------
+
+There are two data structures that are related to managing cron itself and
+cron tasks:
+
+.. code-block:: yaml
+
+ linux:
+ system:
+ cron:
+
+and
+
+.. code-block:: yaml
+
+ linux:
+ system:
+ job:
+
+`linux:system:cron` manages cron packages, services, and '/etc/cron.allow' file.
+
+'deny' files are managed the only way - we're ensuring they are absent, that's
+a requirement from CIS 5.1.8
+
+'cron' pillar structure is the following:
+
+.. code-block:: yaml
+
+ linux:
+ system:
+ cron:
+ enabled: true
+ pkgs: [ <cron packages> ]
+ services: [ <cron services> ]
+ user:
+ <username>:
+ enabled: true
+
+To add user to '/etc/cron.allow' use 'enabled' key as shown above.
+
+'/etc/cron.deny' is not managed as CIS 5.1.8 requires it was removed.
+
+A user would be ignored if any of the following is true:
+* user is disabled in `linux:system:user:<username>`
+* user is disabled in `linux:system:cron:user:<username>`
+
+`linux:system:job` manages individual cron tasks.
By default, it will use name as an identifier, unless identifier key is
explicitly set or False (then it will use Salt's default behavior which is
@@ -255,6 +302,32 @@
hour: 2
minute: 0
+Managing 'at' tasks
+-------------------
+
+Pillar for managing `at` tasks is similar to one for `cron` tasks:
+
+.. code-block:: yaml
+
+ linux:
+ system:
+ at:
+ enabled: true
+ pkgs: [ <at packages> ]
+ services: [ <at services> ]
+ user:
+ <username>:
+ enabled: true
+
+To add a user to '/etc/at.allow' use 'enabled' key as shown above.
+
+'/etc/at.deny' is not managed as CIS 5.1.8 requires it was removed.
+
+A user will be ignored if any of the following is true:
+* user is disabled in `linux:system:user:<username>`
+* user is disabled in `linux:system:at:user:<username>`
+
+
Linux security limits (limit sensu user memory usage to max 1GB):
.. code-block:: yaml
diff --git a/linux/files/cron_users.jinja b/linux/files/cron_users.jinja
new file mode 100644
index 0000000..fe47059
--- /dev/null
+++ b/linux/files/cron_users.jinja
@@ -0,0 +1,5 @@
+# This file is managed by Salt, do not edit
+{%- for user_name in users %}
+{{ user_name }}
+{%- endfor %}
+{# IMPORTANT: This file SHOULD ends with a newline #}
\ No newline at end of file
diff --git a/linux/map.jinja b/linux/map.jinja
index c333a89..52d0e70 100644
--- a/linux/map.jinja
+++ b/linux/map.jinja
@@ -85,6 +85,24 @@
},
}, grain='os_family', merge=salt['pillar.get']('linux:system')) %}
+{% set at = salt['grains.filter_by']({
+ 'Debian': {
+ 'enabled': false,
+ 'pkgs': ['at'],
+ 'services': ['atd'],
+ 'user': {}
+ },
+}, grain='os_family', merge=salt['pillar.get']('linux:system:at')) %}
+
+{% set cron = salt['grains.filter_by']({
+ 'Debian': {
+ 'enabled': false,
+ 'pkgs': ['cron'],
+ 'services': ['cron'],
+ 'user': {}
+ },
+}, grain='os_family', merge=salt['pillar.get']('linux:system:cron')) %}
+
{% set banner = salt['grains.filter_by']({
'BaseDefaults': {
'enabled': false,
diff --git a/linux/system/at.sls b/linux/system/at.sls
new file mode 100644
index 0000000..a441d1a
--- /dev/null
+++ b/linux/system/at.sls
@@ -0,0 +1,62 @@
+{%- from "linux/map.jinja" import system, at with context %}
+
+{%- if at.get('enabled', false) %}
+
+at_packages:
+ pkg.installed:
+ - names: {{ at.pkgs }}
+
+at_services:
+ service.running:
+ - enable: true
+ - names: {{ at.services }}
+ - require:
+ - pkg: at_packages
+ {%- if grains.get('noservices') %}
+ - onlyif: /bin/false
+ {%- endif %}
+
+ {%- set allow_users = [] %}
+ {%- for user_name, user_params in at.get('user', {}).items() %}
+ {%- set user_enabled = user_params.get('enabled', false) and
+ system.get('user', {}).get(
+ user_name, {'enabled': true}).get('enabled', true) %}
+ {%- if user_enabled %}
+ {%- do allow_users.append(user_name) %}
+ {%- endif %}
+ {%- endfor %}
+
+etc_at_allow:
+ {%- if allow_users %}
+ file.managed:
+ - name: /etc/at.allow
+ - template: jinja
+ - source: salt://linux/files/cron_users.jinja
+ - user: root
+ - group: root
+ - mode: 0600
+ - defaults:
+ users: {{ allow_users | yaml }}
+ - require:
+ - cron_packages
+ {%- else %}
+ file.absent:
+ - name: /etc/at.allow
+ {%- endif %}
+
+
+{#
+ /etc/at.deny should be absent to comply with
+ CIS 5.1.8 Ensure at/cron is restricted to authorized users
+#}
+etc_at_deny:
+ file.absent:
+ - name: /etc/at.deny
+
+{%- else %}
+
+fake_linux_system_at:
+ test.nop:
+ - comment: Fake state to satisfy 'require sls:linux.system.at'
+
+{%- endif %}
diff --git a/linux/system/cron.sls b/linux/system/cron.sls
new file mode 100644
index 0000000..7f7ae0e
--- /dev/null
+++ b/linux/system/cron.sls
@@ -0,0 +1,87 @@
+{%- from "linux/map.jinja" import system, cron with context %}
+
+{%- if cron.get('enabled', false) %}
+
+cron_packages:
+ pkg.installed:
+ - names: {{ cron.pkgs }}
+
+cron_services:
+ service.running:
+ - enable: true
+ - names: {{ cron.services }}
+ - require:
+ - pkg: cron_packages
+ {%- if grains.get('noservices') %}
+ - onlyif: /bin/false
+ {%- endif %}
+
+ {%- set allow_users = [] %}
+ {%- for user_name, user_params in cron.get('user', {}).items() %}
+ {%- set user_enabled = user_params.get('enabled', false) and
+ system.get('user', {}).get(
+ user_name, {'enabled': true}).get('enabled', true) %}
+ {%- if user_enabled %}
+ {%- do allow_users.append(user_name) %}
+ {%- endif %}
+ {%- endfor %}
+
+etc_cron_allow:
+ {%- if allow_users %}
+ file.managed:
+ - name: /etc/cron.allow
+ - template: jinja
+ - source: salt://linux/files/cron_users.jinja
+ - user: root
+ - group: root
+ - mode: 0600
+ - defaults:
+ users: {{ allow_users | yaml }}
+ - require:
+ - cron_packages
+ {%- else %}
+ file.absent:
+ - name: /etc/cron.allow
+ {%- endif %}
+
+{#
+ /etc/cron.deny should be absent to comply with
+ CIS 5.1.8 Ensure at/cron is restricted to authorized users
+#}
+etc_cron_deny:
+ file.absent:
+ - name: /etc/cron.deny
+
+etc_crontab:
+ file.managed:
+ - name: /etc/crontab
+ - user: root
+ - group: root
+ - mode: 0600
+ - replace: False
+ - require:
+ - cron_packages
+
+etc_cron_dirs:
+ file.directory:
+ - names:
+ - /etc/cron.d
+ - /etc/cron.daily
+ - /etc/cron.hourly
+ - /etc/cron.monthly
+ - /etc/cron.weekly
+ - user: root
+ - group: root
+ - dir_mode: 0600
+ - recurse:
+ - ignore_files
+ - require:
+ - cron_packages
+
+{%- else %}
+
+fake_linux_system_cron:
+ test.nop:
+ - comment: Fake state to satisfy 'require sls:linux.system.cron'
+
+{%- endif %}
diff --git a/linux/system/init.sls b/linux/system/init.sls
index ad3681a..4f97fa0 100644
--- a/linux/system/init.sls
+++ b/linux/system/init.sls
@@ -3,6 +3,8 @@
include:
- linux.system.env
- linux.system.profile
+- linux.system.at
+- linux.system.cron
{%- if system.repo|length > 0 %}
- linux.system.repo
{%- endif %}
diff --git a/linux/system/job.sls b/linux/system/job.sls
index af42b58..4cdb946 100644
--- a/linux/system/job.sls
+++ b/linux/system/job.sls
@@ -3,45 +3,46 @@
include:
- linux.system.user
+- linux.system.cron
-{%- for name, job in system.job.items() %}
+ {%- for name, job in system.job.items() %}
+ {%- set job_user = job.get('user', 'root') %}
linux_job_{{ job.command }}:
- {%- if job.enabled|default(True) %}
+ {%- if job.get('enabled', True) %}
cron.present:
- name: >
{{ job.command }}
- {%- if job.get('identifier', True) %}
+ {%- if job.get('identifier', True) %}
- identifier: {{ job.get('identifier', job.get('name', name)) }}
- {%- endif %}
- - user: {{ job.user|default("root") }}
- {%- if job.minute is defined %}
+ {%- endif %}
+ - user: {{ job_user }}
+ {%- if job.minute is defined %}
- minute: '{{ job.minute }}'
- {%- endif %}
- {%- if job.hour is defined %}
+ {%- endif %}
+ {%- if job.hour is defined %}
- hour: '{{ job.hour }}'
- {%- endif %}
- {%- if job.daymonth is defined %}
+ {%- endif %}
+ {%- if job.daymonth is defined %}
- daymonth: '{{ job.daymonth }}'
- {%- endif %}
- {%- if job.month is defined %}
+ {%- endif %}
+ {%- if job.month is defined %}
- month: '{{ job.month }}'
- {%- endif %}
- {%- if job.dayweek is defined %}
+ {%- endif %}
+ {%- if job.dayweek is defined %}
- dayweek: '{{ job.dayweek }}'
- {%- endif %}
- {%- if job.user|default("root") in system.get('user', {}).keys() %}
+ {%- endif %}
- require:
- - user: system_user_{{ job.user|default("root") }}
- {%- endif %}
- {%- else %}
+ - sls: linux.system.cron
+ {%- if job_user in system.get('user', {}).keys() %}
+ - user: system_user_{{ job_user }}
+ {%- endif %}
+ {%- else %}
cron.absent:
- name: {{ job.command }}
- {%- if job.get('identifier', True) %}
+ {%- if job.get('identifier', True) %}
- identifier: {{ job.get('identifier', job.get('name', name)) }}
+ {%- endif %}
{%- endif %}
- {%- endif %}
-
-{%- endfor %}
-
+ {%- endfor %}
{%- endif %}
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index 555a0cd..0b792b6 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -5,6 +5,20 @@
fqdn: linux.ci.local
system:
enabled: true
+ at:
+ enabled: true
+ user:
+ root:
+ enabled: true
+ testuser:
+ enabled: true
+ cron:
+ enabled: true
+ user:
+ root:
+ enabled: true
+ testuser:
+ enabled: true
cluster: default
name: linux
domain: ci.local