Merge "Fix obsolete parameter."
diff --git a/.gitignore b/.gitignore
index aa8e42a..cc3ab8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
 .kitchen
+.bundle
+bundle/
 tests/build/
 *.swp
 *.pyc
 .ropeproject
+Gemfile*
diff --git a/.kitchen.yml b/.kitchen.yml
index 51d6a26..dce7fe2 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -63,4 +63,17 @@
       pillars-from-files:
         linux.sls: tests/pillar/system.sls
 
+  - name: system_file
+    provisioner:
+      pillars-from-files:
+        linux.sls: tests/pillar/system_file.sls
+      pillars_from_directories:
+        - source: tests/example
+          dest: srv/salt/linux/files/test
+
+  - name: duo
+    provisioner:
+      pillars-from-files:
+        linux.sls: tests/pillar/system_duo.sls
+
 # vim: ft=yaml sw=2 ts=2 sts=2 tw=125
diff --git a/.travis.yml b/.travis.yml
index 78246a5..dce83af 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,6 @@
+language: python
+python:
+- "2.7.13"
 sudo: required
 services:
   - docker
@@ -17,7 +20,8 @@
     gem 'test-kitchen'
     gem 'kitchen-docker'
     gem 'kitchen-inspec'
-    gem 'inspec'
+    gem 'inspec', '<3.0.0'
+    #Version was frozen, because of issues in the version of inspec >3.0.0 -- see https://mirantis.jira.com/browse/PROD-24324 for more info
     gem 'kitchen-salt', :git => 'https://github.com/salt-formulas/kitchen-salt.git'
   - bundle install
 
@@ -28,6 +32,7 @@
   - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2017.7 SUITE=system
   - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2018.3 SUITE=network
   - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2018.3 SUITE=system
+  - PLATFORM=epcim/salt:saltstack-ubuntu-xenial-salt-2018.3 SUITE=duo
   # - PLATFORM=epcim/salt:saltstack-ubuntu-bionic-salt-2017.7 SUITE=network
   # - PLATFORM=epcim/salt:saltstack-ubuntu-bionic-salt-2017.7 SUITE=system
   # - PLATFORM=epcim/salt:saltstack-ubuntu-bionic-salt-2018.3 SUITE=network
diff --git a/README.rst b/README.rst
index 50e1b13..79b8b18 100644
--- a/README.rst
+++ b/README.rst
@@ -50,6 +50,7 @@
             home: '/home/jdoe'
             home_dir_mode: 755
             email: 'jonh@doe.com'
+            unique: false
           jsmith:
             name: 'jsmith'
             enabled: true
@@ -70,6 +71,30 @@
             home: '/home/elizabeth'
             password: "$6$nUI7QEz3$dFYjzQqK5cJ6HQ38KqG4gTWA9eJu3aKx6TRVDFh6BVJxJgFWg2akfAA7f1fCxcSUeOJ2arCO6EEI6XXnHXxG10"
 
+Configure password expiration parameters
+----------------------------------------
+The following login.defs parameters can be overridden per-user:
+
+* PASS_MAX_DAYS
+* PASS_MIN_DAYS
+* PASS_WARN_DAYS
+* INACTIVE
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        ...
+        user:
+          jdoe:
+            name: 'jdoe'
+            enabled: true
+            ...
+            maxdays: <PASS_MAX_DAYS>
+            mindays: <PASS_MIN_DAYS>
+            warndays: <PASS_WARN_DAYS>
+            inactdays: <INACTIVE>
+
 Configure sudo for users and groups under ``/etc/sudoers.d/``.
 This ways ``linux.system.sudo`` pillar map to actual sudo attributes:
 
@@ -442,6 +467,14 @@
             name: /tmp/test.txt
             source: http://example.com/test.txt
 
+    linux:
+      system:
+        file:
+          test2:
+            name: /tmp/test2.txt
+            source: http://example.com/test2.jinja
+            template: jinja
+
 Ensure presence of file by specifying its contents:
 
 .. code-block:: yaml
@@ -805,6 +838,22 @@
                 power/state: "root:power"
             - devices/system/cpu/cpu0/cpufreq/scaling_governor: powersave
 
+Sysfs definition with disabled automatic write. Attributes are saved
+to configuration, but are not applied during the run.
+Thay will be applied automatically after the reboot.
+
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        sysfs:
+          enable_apply: false
+          scheduler:
+            block/sda/queue/scheduler: deadline
+
+.. note:: The `enable_apply` parameter defaults to `True` if not defined.
+
 Huge Pages
 ~~~~~~~~~~~~
 
@@ -902,6 +951,31 @@
                priority: 900
                package: '*'
 
+If you need to add multiple pin rules for one repo, please use new,ordered definition format
+('pinning' definition will be in priotity to use):
+
+.. code-block:: yaml
+
+  linux:
+    system:
+      repo:
+        mcp_saltstack:
+          source: "deb [arch=amd64] http://repo.saltstack.com/apt/ubuntu/16.04/amd64/2017.7/ xenial main"
+          architectures: amd64
+          clean_file: true
+          pinning:
+            10:
+              enabled: true
+              pin: 'release o=SaltStack'
+              priority: 50
+              package: 'libsodium18'
+            20:
+              enabled: true
+              pin: 'release o=SaltStack'
+              priority: 1100
+              package: '*'
+
+
 .. note:: For old Ubuntu releases (<xenial)
           extra packages for apt transport, like ``apt-transport-https``
           may be required to be installed manually.
@@ -1218,6 +1292,13 @@
         enabled: true
         network_manager: true
 
+Execute linux.network.interface state without ifupdown activity:
+
+.. code-block:: bash
+
+   salt-call linux.network.interface pillar='{"linux":{"network":{"noifupdown":True}}}'
+
+
 Linux with default static network interfaces, default gateway
 interface and DNS servers:
 
@@ -1629,6 +1710,23 @@
             export FTP_PROXY=ftp://127.0.3.3:2121
             export NO_PROXY='.local'
 
+
+Configure login.defs parameters
+-------------------------------
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        login_defs:
+          <opt_name>:
+            enabled: true
+            value: <opt_value>
+
+<opt_name> is a configurational option defined in 'man login.defs'.
+<opt_name> is case sensitive, should be UPPERCASE only!
+
+
 Linux with hosts
 
 Parameter ``purge_hosts`` will enforce whole ``/etc/hosts file``,
@@ -2077,6 +2175,31 @@
                 shadow: (&(&(objectClass=person)(uidNumber=*))(unixHomeDirectory=*))
                 group:  (&(objectClass=group)(gidNumber=*))
 
+PAM duo 2FA integration
+
+.. code-block:: yaml
+
+    parameters:
+      linux:
+        system:
+          auth:
+            enabled: true
+            duo:
+              enabled: true
+              duo_host: localhost
+              duo_ikey: DUO-INTEGRATION-KEY
+              duo_skey: DUO-SECRET-KEY
+
+duo package version may be specified (optional)
+
+.. code-block:: yaml
+
+      linux:
+        system:
+          package:
+            duo-unix:
+              version: 1.10.1-0
+
 Disabled multipath (the default setup):
 
 .. code-block:: yaml
@@ -2153,6 +2276,240 @@
                 interface: bond0
                 mac: "ff:ff:ff:ff:ff:ff" (optional)
 
+Check network params on the environment
+---------------------------------------
+
+Grab nics and nics states
+
+.. code-block:: bash
+
+   salt osd001\* net_checks.get_nics
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   osd001.domain.com:
+       |_
+         - bond0
+         - None
+         - 1e:c8:64:42:23:b9
+         - 0
+         - 1500
+       |_
+         - bond1
+         - None
+         - 3c:fd:fe:27:3b:00
+         - 1
+         - 9100
+       |_
+         - fourty1
+         - None
+         - 3c:fd:fe:27:3b:00
+         - 1
+         - 9100
+       |_
+         - fourty2
+         - None
+         - 3c:fd:fe:27:3b:02
+         - 1
+         - 9100
+
+Grab 10G nics PCI addresses for hugepages setup
+
+.. code-block:: bash
+
+   salt cmp001\* net_checks.get_ten_pci
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   cmp001.domain.com:
+       |_
+         - ten1
+         - 0000:19:00.0
+       |_
+         - ten2
+         - 0000:19:00.1
+       |_
+         - ten3
+         - 0000:19:00.2
+       |_
+         - ten4
+         - 0000:19:00.3
+
+Grab ip address for an interface
+
+.. code-block:: bash
+
+   salt cmp001\* net_checks.get_ip iface=one4
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   cmp001.domain.com:
+       10.200.177.101
+
+Grab ip addresses map
+
+.. code-block:: bash
+
+   salt-call net_checks.nodes_addresses
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   local:
+    |_
+      - cid01.domain.com
+      |_
+        |_
+          - pxe
+          - 10.200.177.91
+        |_
+          - control
+          - 10.200.178.91
+    |_
+      - cmn02.domain.com
+      |_
+        |_
+          - storage_access
+          - 10.200.181.67
+        |_
+          - pxe
+          - 10.200.177.67
+        |_
+          - control
+          - 10.200.178.67
+    |_
+      - cmp010.domain.com
+      |_
+        |_
+          - pxe
+          - 10.200.177.110
+        |_
+          - storage_access
+          - 10.200.181.110
+        |_
+          - control
+          - 10.200.178.110
+        |_
+          - vxlan
+          - 10.200.179.110
+
+Verify full mesh connectivity
+
+.. code-block:: bash
+
+   salt-call net_checks.ping_check
+
+**Example of positive system output:**
+
+.. code-block:: bash
+
+   ['PASSED']
+   [INFO    ] ['PASSED']
+   local:
+       True
+
+**Example of system output in case of failure:**
+
+.. code-block:: bash
+
+   FAILED
+   [ERROR   ] FAILED
+   ['control: 10.0.1.92 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.93 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.51 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.102 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.13 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.81 -> 10.0.1.224: Failed']
+   local:
+       False
+
+For this feature to work, please mark addresses with some role.
+Otherwise 'default' role is assumed and mesh would consist of all
+addresses on the environment.
+
+Mesh mark is needed only for interfaces which are enabled and have
+ip address assigned.
+
+Checking dhcp pxe network meaningless, as it is used for salt
+master vs minion communications, therefore treated as checked.
+
+.. code-block:: yaml
+
+   parameters:
+     linux:
+       network:
+         interface:
+           ens3:
+             enabled: true
+             type: eth
+             proto: static
+             address: ${_param:deploy_address}
+             netmask: ${_param:deploy_network_netmask}
+             gateway: ${_param:deploy_network_gateway}
+             mesh: pxe
+
+Check pillars for ip address duplicates
+
+.. code-block:: bash
+
+   salt-call net_checks.verify_addresses
+
+**Example of positive system output:**
+
+.. code-block:: bash
+
+   ['PASSED']
+   [INFO    ] ['PASSED']
+   local:
+       True
+
+**Example of system output in case of failure:**
+
+.. code-block:: bash
+
+   FAILED. Duplicates found
+   [ERROR   ] FAILED. Duplicates found
+   ['gtw01.domain.com', 'gtw02.domain.com', '10.0.1.224']
+   [ERROR   ] ['gtw01.domain.com', 'gtw02.domain.com', '10.0.1.224']
+   local:
+       False
+
+Generate csv report for the env
+
+.. code-block:: bash
+
+   salt -C 'kvm* or cmp* or osd*' net_checks.get_nics_csv \
+     | grep '^\ ' | sed 's/\ *//g' | grep -Ev ^server \
+     | sed '1 i\server,nic_name,ip_addr,mac_addr,link,mtu,chassis_id,chassis_name,port_mac,port_descr'
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   server,nic_name,ip_addr,mac_addr,link,mtu,chassis_id,chassis_name,port_mac,port_descr
+   cmp010.domain.com,bond0,None,b4:96:91:10:5b:3a,1,1500,,,,
+   cmp010.domain.com,bond0.21,10.200.178.110,b4:96:91:10:5b:3a,1,1500,,,,
+   cmp010.domain.com,bond0.22,10.200.179.110,b4:96:91:10:5b:3a,1,1500,,,,
+   cmp010.domain.com,bond1,None,3c:fd:fe:34:ad:22,0,1500,,,,
+   cmp010.domain.com,bond1.24,10.200.181.110,3c:fd:fe:34:ad:22,0,1500,,,,
+   cmp010.domain.com,fourty5,None,3c:fd:fe:34:ad:20,0,9000,,,,
+   cmp010.domain.com,fourty6,None,3c:fd:fe:34:ad:22,0,9000,,,,
+   cmp010.domain.com,one1,None,b4:96:91:10:5b:38,0,1500,,,,
+   cmp010.domain.com,one2,None,b4:96:91:10:5b:39,1,1500,f0:4b:3a:8f:75:40,exnfvaa18-20,548,ge-0/0/22
+   cmp010.domain.com,one3,None,b4:96:91:10:5b:3a,1,1500,f0:4b:3a:8f:75:40,exnfvaa18-20,547,ge-0/0/21
+   cmp010.domain.com,one4,10.200.177.110,b4:96:91:10:5b:3b,1,1500,f0:4b:3a:8f:75:40,exnfvaa18-20,546,ge-0/0/20
+   cmp011.domain.com,bond0,None,b4:96:91:13:6c:aa,1,1500,,,,
+   cmp011.domain.com,bond0.21,10.200.178.111,b4:96:91:13:6c:aa,1,1500,,,,
+   cmp011.domain.com,bond0.22,10.200.179.111,b4:96:91:13:6c:aa,1,1500,,,,
+   ...
+
 Usage
 =====
 
diff --git a/_modules/net_checks.py b/_modules/net_checks.py
new file mode 100644
index 0000000..cb0f4fc
--- /dev/null
+++ b/_modules/net_checks.py
@@ -0,0 +1,279 @@
+from os import listdir, path
+from subprocess import Popen,PIPE
+from re import findall as refindall
+from re import search as research
+import salt.utils
+import socket, struct, fcntl
+import logging
+
+logger = logging.getLogger(__name__)
+stream = logging.StreamHandler()
+logger.addHandler(stream)
+
+def get_ip(iface='ens2'):
+
+    ''' Get ip address from an interface if applicable
+
+    :param iface: Interface name. Type: str
+
+    '''
+
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    sockfd = sock.fileno()
+    SIOCGIFADDR = 0x8915
+    ifreq = struct.pack('16sH14s', iface, socket.AF_INET, '\x00'*14)
+
+    try:
+        res = fcntl.ioctl(sockfd, SIOCGIFADDR, ifreq)
+    except:
+        logger.debug("No ip addresses assigned to %s" % iface)
+        return None
+
+    ip = struct.unpack('16sH2x4s8x', res)[2]
+    return socket.inet_ntoa(ip)
+
+def get_nics():
+
+    ''' List nics '''
+
+    nics = []
+    nics_list = listdir('/sys/class/net/')
+    for nic_name in nics_list:
+        if research('(br|bond|ens|enp|eth|one|ten|fourty)[0-9]+', nic_name):
+
+            # Interface should be in "up" state in order to get carrier status
+            Popen("ip li set dev " + nic_name + " up", shell=True, stdout=PIPE)
+
+            with open("/sys/class/net/" + nic_name + "/carrier", 'r') as f:
+                try:
+                    carrier = int(f.read())
+                except:
+                    carrier = 0
+
+            bond = ""
+            if path.isfile("/sys/class/net/" + nic_name + "/master/uevent"):
+                with open("/sys/class/net/" + nic_name + "/master/uevent", 'r') as f:
+                    for line in f:
+                        sline = line.strip()
+                        if 'INTERFACE=bond' in sline:
+                            bond = sline.split('=')[1]
+            if len(bond) == 0:
+                with open("/sys/class/net/" + nic_name + "/address", 'r') as f:
+                    macaddr = f.read().strip()
+            else:
+                with open("/proc/net/bonding/" + bond, 'r') as f:
+                    line = f.readline()
+                    if_struct = False
+                    while line:
+                        sline = line.strip()
+                        if 'Slave Interface: ' + nic_name in sline and not if_struct:
+                            if_struct = True
+                        if 'Permanent HW addr: ' in sline and if_struct:
+                            macaddr = sline.split()[3]
+                            break
+                        line = f.readline()
+
+            with open("/sys/class/net/" + nic_name + "/mtu", 'r') as f:
+                mtu = f.read()
+
+            ip = str(get_ip(nic_name))
+
+            nics.append([nic_name, ip, macaddr, carrier, mtu])
+
+    return sorted(nics)
+
+def get_ten_pci():
+
+    ''' List ten nics pci addresses '''
+
+    nics = []
+    nics_list = listdir('/sys/class/net/')
+    for nic_name in nics_list:
+        if research('ten[0-9]+', nic_name):
+            with open("/sys/class/net/" + nic_name + "/device/uevent", 'r') as f:
+                for line in f:
+                    sline = line.strip()
+                    if "PCI_SLOT_NAME=" in sline:
+                        nics.append([nic_name , sline.split("=")[1]])
+
+    return sorted(nics)
+
+def mesh_ping(mesh):
+
+    ''' One to many ICMP check
+
+    :param hosts: Target hosts. Type: list of ip addresses
+
+    '''
+
+    io = []
+    minion_id = __salt__['config.get']('id')
+
+    for host, hostobj in mesh:
+        if host == minion_id:
+            for mesh_net, addr, targets in hostobj:
+                if addr in targets:
+                    targets.remove(addr)
+                for tgt in targets:
+                    # This one will run in parallel with everyone else
+                    worker = Popen("ping -c 1 -w 1 -W 1 " + str(tgt), \
+                        shell=True, stdout=PIPE, stderr=PIPE)
+                    ping_out = worker.communicate()[0]
+                    if worker.returncode != 0:
+                        io.append(mesh_net + ': ' + addr + ' -> ' + tgt + ': Failed')
+
+    return io
+
+def minion_list():
+
+    ''' List registered minions '''
+
+    return listdir('/etc/salt/pki/master/minions/')
+
+def verify_addresses():
+
+    ''' Verify addresses taken from pillars '''
+
+    nodes = nodes_addresses()
+    verifier = {}
+    failed = []
+
+    for node, nodeobj in nodes:
+        for item in nodeobj:
+            addr = item[1]
+            if addr in verifier:
+                failed.append([node,verifier[addr],addr])
+            else:
+                verifier[addr] = node
+
+    if failed:
+        logger.error("FAILED. Duplicates found")
+        logger.error(failed)
+        return False
+    else:
+        logger.setLevel(logging.INFO)
+        logger.info(["PASSED"])
+        return True
+
+def nodes_addresses():
+
+    ''' List servers addresses '''
+
+    compound = 'linux:network:interface'
+    out = __salt__['saltutil.cmd']( tgt='I@' + compound,
+                                    tgt_type='compound',
+                                    fun='pillar.get',
+                                    arg=[compound],
+                                    timeout=10
+                                  ) or None
+
+    servers = []
+    for minion in minion_list():
+        addresses = []
+        if minion in out:
+            ifaces = out[minion]['ret']
+            for iface in ifaces:
+                ifobj = ifaces[iface]
+                if ifobj['enabled'] and 'address' in ifobj:
+                    if 'mesh' in ifobj:
+                        mesh = ifobj['mesh']
+                    else:
+                        mesh = 'default'
+                    addresses.append([mesh, ifobj['address']])
+            servers.append([minion,addresses])
+
+    return servers
+
+def get_mesh():
+
+    ''' Build addresses mesh '''
+
+    full_mesh = {}
+    nodes = nodes_addresses()
+
+    for node, nodeobj in nodes:
+        for item in nodeobj:
+            mesh = item[0]
+            addr = item[1]
+            if not mesh in full_mesh:
+                full_mesh[mesh] = []
+            full_mesh[mesh].append(addr)
+
+    for node, nodeobj in nodes:
+        for item in nodeobj:
+            mesh = item[0]
+            tgts = full_mesh[mesh]
+            item.append(tgts)
+
+    return nodes
+
+def ping_check():
+
+    ''' Ping addresses in a mesh '''
+
+    mesh = get_mesh()
+    out = __salt__['saltutil.cmd']( tgt='*',
+                                    tgt_type='glob',
+                                    fun='net_checks.mesh_ping',
+                                    arg=[mesh],
+                                    timeout=10
+                                  ) or None
+
+    failed = []
+
+    if out:
+        for minion in out:
+            ret = out[minion]['ret']
+            if ret:
+                failed.append(ret)
+    else:
+        failed = ["No response from minions"]
+
+    if failed:
+        logger.error("FAILED")
+        logger.error('\n'.join(str(x) for x in failed))
+        return False
+    else:
+        logger.setLevel(logging.INFO)
+        logger.info(["PASSED"])
+        return True
+
+def get_nics_csv(delim=","):
+
+    ''' List nics in csv format
+
+    :param delim: Delimiter char. Type: str
+
+    '''
+
+    header = "server,nic_name,ip_addr,mac_addr,link,chassis_id,chassis_name,port_mac,port_descr\n"
+    io = ""
+
+    # Try to reuse lldp output if possible
+    try:
+        lldp_info = Popen("lldpcli -f keyvalue s n s", shell=True, stdout=PIPE).communicate()[0]
+    except:
+        lldp_info = ""
+
+    for nic in get_nics():
+        lldp = ""
+        nic_name = nic[0]
+        if research('(one|ten|fourty)[0-9]+', nic_name):
+            # Check if we can fetch lldp data for that nic
+            for line in lldp_info.splitlines():
+                chassis = 'lldp.' + nic[0] + '.chassis'
+                port = 'lldp.' + nic[0] + '.port'
+                if chassis in line or port in line:
+                    lldp += delim + line.split('=')[1]
+        if not lldp:
+            lldp = delim + delim + delim + delim
+
+        io += __salt__['config.get']('id') + \
+              delim + nic_name + \
+              delim + str(nic[1]).strip() + \
+              delim + str(nic[2]).strip() + \
+              delim + str(nic[3]).strip() + \
+              delim + str(nic[4]).strip() + \
+              lldp + "\n"
+
+    return header + io
diff --git a/_modules/ovs_config.py b/_modules/ovs_config.py
new file mode 100644
index 0000000..3122f71
--- /dev/null
+++ b/_modules/ovs_config.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+'''
+Support for Open vSwitch database configuration.
+
+'''
+from __future__ import absolute_import
+
+import logging
+import salt.utils
+
+log = logging.getLogger(__name__)
+
+
+def __virtual__():
+    '''
+    Only load the module if Open vSwitch is installed
+    '''
+    if salt.utils.which('ovs-vsctl'):
+        return 'ovs_config'
+    return False
+
+
+def _retcode_to_bool(retcode):
+    '''
+    Evaulates ovs-vsctl command`s retcode value.
+
+    Args:
+        retcode: Value of retcode field from response.
+    '''
+    return True if retcode == 0 else False
+
+
+def set(cfg, value, wait=True):
+    '''
+    Updates a specified configuration entry.
+
+    Args:
+        cfg/value: a config entry to update
+        wait: wait or not for ovs-vswitchd to reconfigure itself before it exits.
+
+    CLI Example:
+    .. code-block:: bash
+
+        salt '*' ovs_config.set other_config:dpdk-init true
+    '''
+    wait = '' if wait else '--no-wait '
+
+    cmd = 'ovs-vsctl {0}set Open_vSwitch . {1}="{2}"'.format(wait, cfg, str(value).lower())
+    result = __salt__['cmd.run_all'](cmd)
+    return _retcode_to_bool(result['retcode'])
+
+
+def remove(cfg):
+    '''
+    Removes a specified configuration entry.
+
+    Args:
+        cfg: a config entry to remove
+
+    CLI Example:
+    .. code-block:: bash
+
+        salt '*' ovs_config.remove other_config
+    '''
+    if ':' in cfg:
+        section, key = cfg.split(':')
+        cmd = 'ovs-vsctl remove Open_vSwitch . {} {}'.format(section, key)
+    else:
+        cmd = 'ovs-vsctl clear Open_vSwitch . ' + cfg
+
+    result = __salt__['cmd.run_all'](cmd)
+    return _retcode_to_bool(result['retcode'])
+
+
+def list():
+    '''
+    Return a current config of Open vSwitch
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' ovs_config.list
+    '''
+    cmd = 'ovs-vsctl list Open_vSwitch .'
+    result = __salt__['cmd.run_all'](cmd)
+
+    if result['retcode'] == 0:
+        config = {}
+        for l in result['stdout'].splitlines():
+            cfg, value = map((lambda x: x.strip()), l.split(' : '))
+            if value.startswith('{') and len(value) > 2:
+                for i in value[1:-1].replace('"', '').split(', '):
+                    _k, _v = i.split('=')
+                    config['{}:{}'.format(cfg,_k)] = _v
+            else:
+                config[cfg] = value
+
+        return config
+    else:
+        return False
diff --git a/_states/ovs_config.py b/_states/ovs_config.py
new file mode 100644
index 0000000..bcc8ad6
--- /dev/null
+++ b/_states/ovs_config.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+'''
+Management of Open vSwitch configuration
+========================================
+
+The OVS config can be managed with the ovs_config state module:
+
+.. code-block:: yaml
+
+    other_config:dpdk-init:
+      ovs_config.present:
+        - value: True
+
+    other_config:dpdk-extra:
+      ovs_config.present:
+        - value: -n 12 --vhost-owner libvirt-qemu:kvm --vhost-perm 0664
+
+    external_ids:
+      ovs_config.absent
+'''
+
+
+def __virtual__():
+    '''
+    Only make these states available if Open vSwitch is installed.
+    '''
+    return 'ovs_config.list' in __salt__
+
+
+def present(name, value, wait=True):
+    '''
+    Ensures that the named config exists, eventually creates it.
+
+    Args:
+        name/value: The name/value of the config entry.
+        wait: Whether wait for ovs-vswitchd to reconfigure itself according to the modified database.
+    '''
+    ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
+    ovs_config = __salt__['ovs_config.list']()
+
+    if name in ovs_config and ovs_config[name] == str(value).lower():
+        ret['result'] = True
+        ret['comment'] = '{0} is already set to {1}.'.format(name, value)
+    else:
+        config_updated = __salt__['ovs_config.set'](name, value, wait)
+        if config_updated:
+            ret['result'] = True
+            ret['comment'] = '{0} is updated.'.format(name)
+            ret['changes'] = { name: 'Updated to {0}'.format(value) }
+        else:
+            ret['result'] = False
+            ret['comment'] = 'Unable to update config of {0}.'.format(name)
+
+    return ret
+
+
+def absent(name):
+    '''
+    Ensures that the named config does not exist, eventually deletes it.
+
+    Args:
+        name: The name of the config entry.
+
+    '''
+    ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
+    ovs_config = __salt__['ovs_config.list']()
+
+    if ':' in name and name not in ovs_config:
+        ret['result'] = True
+        ret['comment'] = '{0} does not exist.'.format(name)
+    else:
+        config_removed = __salt__['ovs_config.remove'](name)
+        if config_removed:
+            ret['result'] = True
+            ret['comment'] = '{0} is removed.'.format(name)
+            ret['changes'] = { name: '{0} removed'.format(name) }
+        else:
+            ret['result'] = False
+            ret['comment'] = 'Unable to delete config of {0}.'.format(name)
+
+    return ret
diff --git a/linux/files/login.defs.jinja b/linux/files/login.defs.jinja
new file mode 100644
index 0000000..572c558
--- /dev/null
+++ b/linux/files/login.defs.jinja
@@ -0,0 +1,62 @@
+{%- from "linux/map.jinja" import login_defs with context -%}
+# This file is managed by Salt, do not edit
+{%- set allowed_options = [
+    'CHFN_RESTRICT',
+    'CONSOLE_GROUPS',
+    'CREATE_HOME',
+    'DEFAULT_HOME',
+    'ENCRYPT_METHOD',
+    'ENV_HZ',
+    'ENV_PATH',
+    'ENV_SUPATH',
+    'ERASECHAR',
+    'FAIL_DELAY',
+    'FAKE_SHELL',
+    'GID_MAX',
+    'GID_MIN',
+    'HUSHLOGIN_FILE',
+    'KILLCHAR',
+    'LOG_OK_LOGINS',
+    'LOG_UNKFAIL_ENAB',
+    'LOGIN_RETRIES',
+    'LOGIN_TIMEOUT',
+    'MAIL_DIR',
+    'MAIL_FILE',
+    'MAX_MEMBERS_PER_GROUP',
+    'MD5_CRYPT_ENAB',
+    'PASS_MAX_DAYS',
+    'PASS_MIN_DAYS',
+    'PASS_WARN_AGE',
+    'SHA_CRYPT_MIN_ROUNDS',
+    'SHA_CRYPT_MAX_ROUNDS',
+    'SULOG_FILE',
+    'SU_NAME',
+    'SUB_GID_MIN',
+    'SUB_GID_MAX',
+    'SUB_GID_COUNT',
+    'SUB_UID_MIN',
+    'SUB_UID_MAX',
+    'SUB_UID_COUNT',
+    'SYS_GID_MAX',
+    'SYS_GID_MIN',
+    'SYS_UID_MAX',
+    'SYS_UID_MIN',
+    'SYSLOG_SG_ENAB',
+    'SYSLOG_SU_ENAB',
+    'TTYGROUP',
+    'TTYPERM',
+    'TTYTYPE_FILE',
+    'UID_MAX',
+    'UID_MIN',
+    'UMASK',
+    'USERDEL_CMD',
+    'USERGROUPS_ENAB'
+] %}
+{%- for opt_name in allowed_options %}
+  {%- if opt_name in login_defs %}
+    {%- set opt_params = login_defs.get(opt_name) %}
+    {%- if opt_params.get('enabled', true) %}
+{{ opt_name.ljust(20) }} {{ opt_params.value }}
+    {%- endif %}
+  {%- endif %}
+{%- endfor %}
diff --git a/linux/files/login_duo.conf b/linux/files/login_duo.conf
new file mode 100644
index 0000000..914ce8a
--- /dev/null
+++ b/linux/files/login_duo.conf
@@ -0,0 +1,8 @@
+{%- from "linux/map.jinja" import auth with context %}
+[duo]
+ikey = {{ auth.duo.duo_ikey }}
+skey = {{ auth.duo.duo_skey }}
+host = {{ auth.duo.duo_host }}
+pushinfo = yes
+failmode = secure
+
diff --git a/linux/files/ovs_bridge b/linux/files/ovs_bridge
new file mode 100644
index 0000000..bf9b74f
--- /dev/null
+++ b/linux/files/ovs_bridge
@@ -0,0 +1,14 @@
+auto {{ bridge_name }}
+iface {{ bridge_name }} inet {{ bridge.get('proto', 'static' if bridge.address is defined else 'manual') }}
+ovs_type {{ bridge.get('ovs_bridge_type', 'OVSBridge') }}
+mtu {{ bridge.get('mtu', '1500') }}
+{%- if bridge.address is defined %}
+address {{ bridge.address }}
+netmask {{ bridge.netmask }}
+{%- endif %}
+{%- if bridge.gateway is defined %}
+gateway {{ bridge.gateway }}
+{%- endif %}
+{%- if bridge.ovs_options is defined %}
+ovs_options {{ bridge.ovs_options }}
+{%- endif %}
diff --git a/linux/files/pam-sshd b/linux/files/pam-sshd
new file mode 100644
index 0000000..1aeb502
--- /dev/null
+++ b/linux/files/pam-sshd
@@ -0,0 +1,67 @@
+{%- from "linux/map.jinja" import auth with context %}
+
+# PAM configuration for the Secure Shell service
+
+{%- if auth.duo.enabled %}
+auth required /lib64/security/pam_duo.so
+account required pam_nologin.so
+
+# Standard Un*x authentication.
+#@include common-auth
+{%- else %}
+# Standard Un*x authentication.
+@include common-auth
+{%- endif %}
+
+# Disallow non-root logins when /etc/nologin exists.
+account    required     pam_nologin.so
+
+# Uncomment and edit /etc/security/access.conf if you need to set complex
+# access limits that are hard to express in sshd_config.
+# account  required     pam_access.so
+
+# Standard Un*x authorization.
+@include common-account
+
+# SELinux needs to be the first session rule.  This ensures that any
+# lingering context has been cleared.  Without this it is possible that a
+# module could execute code in the wrong domain.
+session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so close
+
+# Set the loginuid process attribute.
+session    required     pam_loginuid.so
+
+# Create a new session keyring.
+session    optional     pam_keyinit.so force revoke
+
+# Standard Un*x session setup and teardown.
+@include common-session
+
+# Print the message of the day upon successful login.
+# This includes a dynamically generated part from /run/motd.dynamic
+# and a static (admin-editable) part from /etc/motd.
+session    optional     pam_motd.so  motd=/run/motd.dynamic
+session    optional     pam_motd.so noupdate
+
+# Print the status of the user's mailbox upon successful login.
+session    optional     pam_mail.so standard noenv # [1]
+
+# Set up user limits from /etc/security/limits.conf.
+session    required     pam_limits.so
+
+
+# Read environment variables from /etc/environment and
+# /etc/security/pam_env.conf.
+session    required     pam_env.so # [1]
+# In Debian 4.0 (etch), locale-related environment variables were moved to
+# /etc/default/locale, so read that as well.
+session    required     pam_env.so user_readenv=1 envfile=/etc/default/locale
+
+# SELinux needs to intervene at login time to ensure that the process starts
+# in the proper default security context.  Only sessions which are intended
+# to run in the user's context should be run after this.
+session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so open
+
+# Standard Un*x password updating.
+@include common-password
+
diff --git a/linux/files/preferences_repo b/linux/files/preferences_repo
index 603d313..91e9f9b 100644
--- a/linux/files/preferences_repo
+++ b/linux/files/preferences_repo
@@ -1,8 +1,19 @@
-{%- from "linux/map.jinja" import system with context %}
-{%- set repo = system.repo[repo_name] %}
-{%- for pin in repo.pin %}
-{%- set package = pin.get('package', '*') %}
+{%- from "linux/map.jinja" import system with context -%}
+{%- set repo = system.repo[repo_name] -%}
+{%- if repo.pinning is defined  -%}
+  {%- for id,pin in repo.pinning|dictsort -%}
+    {% if pin.get('enabled', False) %}
+
+Package: {{ pin.get('package','*') }}
+Pin: {{ pin.pin }}
+Pin-Priority: {{ pin.priority }}
+    {%- endif %}
+  {%- endfor -%}
+{%- elif repo.pin is defined -%}
+  {%- for pin in repo.pin -%}
+    {%- set package = pin.get('package', '*') %}
 Package: {{ package }}
 Pin: {{ pin.pin }}
 Pin-Priority: {{ pin.priority }}
-{% endfor %}
+  {%- endfor %}
+{%- endif -%}
diff --git a/linux/map.jinja b/linux/map.jinja
index 52d0e70..667a2dd 100644
--- a/linux/map.jinja
+++ b/linux/map.jinja
@@ -112,12 +112,30 @@
 {% set auth = salt['grains.filter_by']({
     'Arch': {
         'enabled': false,
+        'duo': {
+            'enabled': false,
+            'duo_host': 'localhost',
+            'duo_ikey': '',
+            'duo_skey': ''
+        }
     },
     'RedHat': {
         'enabled': false,
+        'duo': {
+            'enabled': false,
+            'duo_host': 'localhost',
+            'duo_ikey': '',
+            'duo_skey': ''
+        }
     },
     'Debian': {
         'enabled': false,
+        'duo': {
+            'enabled': false,
+            'duo_host': 'localhost',
+            'duo_ikey': '',
+            'duo_skey': ''
+        }
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:system:auth')) %}
 
@@ -140,6 +158,70 @@
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:system:auth:ldap')) %}
 
+{%- load_yaml as login_defs_defaults %}
+Debian:
+    CHFN_RESTRICT:
+        value: 'rwh'
+    DEFAULT_HOME:
+        value: 'yes'
+    ENCRYPT_METHOD:
+        value: 'SHA512'
+    ENV_PATH:
+        value: 'PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games'
+    ENV_SUPATH:
+        value: 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+    ERASECHAR:
+        value: '0177'
+    FAILLOG_ENAB:
+        value: 'yes'
+    FTMP_FILE:
+        value: '/var/log/btmp'
+    GID_MAX:
+        value: '60000'
+    GID_MIN:
+        value: '1000'
+    HUSHLOGIN_FILE:
+        value: '.hushlogin'
+    KILLCHAR:
+        value: '025'
+    LOGIN_RETRIES:
+        value: '5'
+    LOGIN_TIMEOUT:
+        value: '60'
+    LOG_OK_LOGINS:
+        value: 'no'
+    LOG_UNKFAIL_ENAB:
+        value: 'no'
+    MAIL_DIR:
+        value: '/var/mail'
+    PASS_MAX_DAYS:
+        value: '99999'
+    PASS_MIN_DAYS:
+        value: '0'
+    PASS_WARN_AGE:
+        value: '7'
+    SU_NAME:
+        value: 'su'
+    SYSLOG_SG_ENAB:
+        value: 'yes'
+    SYSLOG_SU_ENAB:
+        value: 'yes'
+    TTYGROUP:
+        value: 'tty'
+    TTYPERM:
+        value: '0600'
+    UID_MAX:
+        value: '60000'
+    UID_MIN:
+        value: '1000'
+    UMASK:
+        value: '022'
+    USERGROUPS_ENAB:
+        value: 'yes'
+{%- endload %}
+{%- set login_defs = salt['grains.filter_by'](login_defs_defaults,
+    grain='os_family', merge=salt['pillar.get']('linux:system:login_defs')) %}
+
 {#    'network_name', #}
 
 {% set interface_params = [
@@ -367,8 +449,8 @@
             'warn': 5,
         },
         'net_rx_action_per_cpu_threshold': {
-            'warning': '0',
-            'minor': '100'
+            'warning': '500',
+            'minor': '5000'
         },
         'packets_dropped_per_cpu_threshold': {
             'minor': '0',
@@ -376,3 +458,4 @@
         }
     },
 }, grain='os_family', merge=salt['pillar.get']('linux:monitoring')) %}
+
diff --git a/linux/meta/fluentd.yml b/linux/meta/fluentd.yml
index a6f9cc4..f6d6720 100644
--- a/linux/meta/fluentd.yml
+++ b/linux/meta/fluentd.yml
@@ -77,6 +77,8 @@
             record:
               - name: severity_label
                 value: '${ {"TRACE"=>8,"DEBUG"=>7,"INFO"=>6,"NOTICE"=>5,"WARNING"=>4,"ERROR"=>3,"CRITICAL"=>2,"ALERT"=>1,"EMERGENCY"=>0}.key(record["Severity"].to_i) }'
+              - name: source
+                value: systemd
         match:
           rewrite_tag:
             tag: systemd.source
@@ -84,9 +86,9 @@
             rule:
               - name: ident
                 regexp: '^(.*)$'
-                result: __TAG__.$1
+                result: $1.systemd
           push_to_default:
-            tag: 'systemd.source.*'
+            tag: '*.systemd'
             type: copy
             store:
               - type: relabel
diff --git a/linux/meta/prometheus.yml b/linux/meta/prometheus.yml
index 3ca2b26..5a3ca05 100644
--- a/linux/meta/prometheus.yml
+++ b/linux/meta/prometheus.yml
@@ -234,24 +234,24 @@
       {%- endraw %}
       {%- set net_rx_action_warning_threshold = monitoring.net_rx_action_per_cpu_threshold.warning %}
       if: >-
-        floor(increase(nstat_time_squeeze[24h])) > {{ net_rx_action_warning_threshold }}
+        floor(increase(nstat_time_squeeze[1d])) > {{ net_rx_action_warning_threshold }}
       labels:
         severity: warning
         service: system
       annotations:
         summary: "CPU terminated {{ net_rx_action_warning_threshold }}{%- raw %} net_rx_action loops"
-        description: "The {{ $labels.cpu }} CPU on the {{ $labels.host }} node terminated {{ $value }} net_rx_action loops during the last 24 hours."
+        description: "The {{ $labels.cpu }} CPU on the {{ $labels.host }} node terminated {{ $value }} net_rx_action loops during the last 24 hours. Modify the net.core.netdev_budget kernel parameter."
     NetRxActionByCpuMinor:
       {%- endraw %}
       {%- set net_rx_action_minor_threshold = monitoring.net_rx_action_per_cpu_threshold.minor %}
       if: >-
-        floor(increase(nstat_time_squeeze[24h])) > {{ net_rx_action_minor_threshold }}
+        floor(increase(nstat_time_squeeze[1d])) > {{ net_rx_action_minor_threshold }}
       labels:
         severity: minor
         service: system
       annotations:
         summary: "CPU terminated {{ net_rx_action_minor_threshold }}{%- raw %} net_rx_action loops"
-        description: "The {{ $labels.cpu }} CPU on the {{ $labels.host }} node terminated {{ $value }} net_rx_action loops during the last 24 hours."
+        description: "The {{ $labels.cpu }} CPU on the {{ $labels.host }} node terminated {{ $value }} net_rx_action loops during the last 24 hours. Modify the net.core.netdev_budget kernel parameter."
 {%- endraw %}
 {%- if monitoring.bond_status.interfaces is defined and monitoring.bond_status.interfaces %}
 {%- raw %}
diff --git a/linux/network/dpdk.sls b/linux/network/dpdk.sls
index 2c7dcb9..7cc4102 100644
--- a/linux/network/dpdk.sls
+++ b/linux/network/dpdk.sls
@@ -106,7 +106,7 @@
 linux_network_dpdk_bond_interface_{{ interface_name }}:
   cmd.run:
     - name: "ovs-vsctl{%- if network.ovs_nowait %} --no-wait{%- endif %} add-bond {{ interface.bridge }} {{ interface_name }} {{ bond_interfaces.keys()|join(' ') }}"
-    - unless: "ovs-vsctl show | grep {{ interface_name }}"
+    - unless: "ovs-vsctl list-ports {{ interface.bridge }} | grep -w {{ interface_name }}"
     - require:
       - cmd: linux_network_dpdk_bridge_interface_{{ interface.bridge }}
 
@@ -143,43 +143,12 @@
     - name: "ovs-vsctl{%- if network.ovs_nowait %} --no-wait{%- endif %} add-br {{ interface_name }} -- set bridge {{ interface_name }} datapath_type=netdev{% if interface.tag is defined %} -- set port {{ interface_name }} tag={{ interface.tag }}{% endif %}"
     - unless: "ovs-vsctl show | grep {{ interface_name }}"
 
-    {# OVS dpdk needs ip address for vxlan termination on bridge br-prv #}
-    {%- if interface.address is defined %}
-
-{# create override for openvswitch dependency for dpdk br-prv #}
-/etc/systemd/system/ifup@{{ interface_name }}.service.d/override.conf:
-  file.managed:
-    - makedirs: true
-    - require:
-      - cmd: linux_network_dpdk_bridge_interface_{{ interface_name }}
-    - contents: |
-        [Unit]
-        Requires=openvswitch-switch.service
-        After=openvswitch-switch.service
-
-{# enforce ip address and mtu for ovs dpdk br-prv #}
-/etc/network/interfaces.u/ifcfg-{{ interface_name }}:
-  file.managed:
-    - contents: |
-        auto {{ interface_name }}
-        iface {{ interface_name }} inet static
-        address {{ interface.address }}
-        netmask {{ interface.netmask }}
-        {%- if interface.mtu is defined %}
-        mtu {{ interface.mtu }}
-        {%- endif %}
-    - makedirs: True
-    - require:
-      - file: /etc/systemd/system/ifup@{{ interface_name }}.service.d/override.conf
-
-    {%- endif %}
-
   {%- elif interface.type == 'dpdk_ovs_port' and interface.bridge is defined %}
 
 linux_network_dpdk_bridge_port_interface_{{ interface_name }}:
   cmd.run:
-    - name: "ovs-vsctl{%- if network.ovs_nowait %} --no-wait{%- endif %} add-port {{ interface.bridge }} dpdk0 -- set Interface dpdk0 type=dpdk options:dpdk-devargs={{ interface.pci }}"
-    - unless: "ovs-vsctl show | grep dpdk0"
+    - name: "ovs-vsctl{%- if network.ovs_nowait %} --no-wait{%- endif %} add-port {{ interface.bridge }} {{ interface_name }} -- set Interface {{ interface_name }} type=dpdk options:dpdk-devargs={{ interface.pci }}"
+    - unless: "ovs-vsctl list-ports {{ interface.bridge }} | grep -w {{ interface_name }}"
     - require:
       - cmd: linux_network_dpdk_bridge_interface_{{ interface.bridge }}
 
diff --git a/linux/network/interface.sls b/linux/network/interface.sls
index c2d2a23..6086957 100644
--- a/linux/network/interface.sls
+++ b/linux/network/interface.sls
@@ -2,7 +2,8 @@
 {%- from "linux/map.jinja" import system with context %}
 {%- if network.enabled %}
 
-{%- if network.get('dpdk', {}).get('enabled', False) %}
+{%- set dpdk_enabled = network.get('dpdk', {}).get('enabled', False) %}
+{%- if dpdk_enabled %}
 include:
 - linux.network.dpdk
 {%- endif %}
@@ -76,9 +77,45 @@
   cmd.run:
     - unless: ovs-vsctl show | grep -w {{ int_name }}
     - name: ovs-vsctl{%- if network.ovs_nowait %} --no-wait{%- endif %} add-port {{ interface_name }} {{ int_name }}
-
 {%- endif %}
 {%- endfor %}
+
+linux_interfaces_include_{{ interface_name }}:
+  file.prepend:
+  - name: /etc/network/interfaces
+  - text: |
+      source /etc/network/interfaces.d/*
+      # Workaround for Upstream-Bug: https://github.com/saltstack/salt/issues/40262
+      source /etc/network/interfaces.u/*
+
+{# create override for openvswitch dependency for dpdk br-prv #}
+/etc/systemd/system/ifup@{{ interface_name }}.service.d/override.conf:
+  file.managed:
+    - makedirs: true
+    - require:
+      - cmd: linux_network_dpdk_bridge_interface_{{ interface_name }}
+    - contents: |
+        [Unit]
+        Requires=openvswitch-switch.service
+        After=openvswitch-switch.service
+
+dpdk_ovs_bridge_{{ interface_name }}:
+  file.managed:
+  - name: /etc/network/interfaces.u/ifcfg-{{ interface_name }}
+  - makedirs: True
+  - source: salt://linux/files/ovs_bridge
+  - defaults:
+      bridge: {{ interface|yaml }}
+      bridge_name: {{ interface_name }}
+  - template: jinja
+
+dpdk_ovs_bridge_up_{{ interface_name }}:
+  cmd.run:
+  - name: ifup {{ interface_name }}
+  - require:
+    - file: dpdk_ovs_bridge_{{ interface_name }}
+    - file: linux_interfaces_final_include
+
 {%- endif %}
 
 {# it is not used for any interface with type preffix dpdk,eg. dpdk_ovs_port #}
@@ -88,7 +125,7 @@
 
 {%- if interface.type == 'ovs_bridge' %}
 
-ovs_bridge_{{ interface_name }}:
+ovs_bridge_{{ interface_name }}_present:
   openvswitch_bridge.present:
   - name: {{ interface_name }}
 
@@ -103,24 +140,49 @@
   cmd.run:
     - unless: ovs-vsctl show | grep {{ int_name }}
     - name: ovs-vsctl{%- if network.ovs_nowait %} --no-wait{%- endif %} add-port {{ interface_name }} {{ int_name }}
-
 {%- endif %}
-
 {%- endfor %}
 
+linux_interfaces_include_{{ interface_name }}:
+  file.prepend:
+  - name: /etc/network/interfaces
+  - text: |
+      source /etc/network/interfaces.d/*
+      # Workaround for Upstream-Bug: https://github.com/saltstack/salt/issues/40262
+      source /etc/network/interfaces.u/*
+
+ovs_bridge_{{ interface_name }}:
+  file.managed:
+  - name: /etc/network/interfaces.u/ifcfg-{{ interface_name }}
+  - makedirs: True
+  - source: salt://linux/files/ovs_bridge
+  - defaults:
+      bridge: {{ interface|yaml }}
+      bridge_name: {{ interface_name }}
+  - template: jinja
+
+ovs_bridge_up_{{ interface_name }}:
+  cmd.run:
+  - name: ifup {{ interface_name }}
+  - require:
+    - file: ovs_bridge_{{ interface_name }}
+    - file: linux_interfaces_final_include
+
+
+
 {%- elif interface.type == 'ovs_port' %}
 
 {%- if interface.get('port_type','internal') == 'patch' %}
 
-ovs_port_{{ interface_name }}:
+ovs_port_{{ interface_name }}_present:
   openvswitch_port.present:
   - name: {{ interface_name }}
   - bridge: {{ interface.bridge }}
   - require:
-    {%- if network.interface.get(interface.bridge, {}).get('type', 'ovs_bridge') == 'dpdk_ovs_bridge' %}
+    {%- if dpdk_enabled and network.interface.get(interface.bridge, {}).get('type', 'ovs_bridge') == 'dpdk_ovs_bridge' %}
     - cmd: linux_network_dpdk_bridge_interface_{{ interface.bridge }}
     {%- else %}
-    - openvswitch_bridge: ovs_bridge_{{ interface.bridge }}
+    - openvswitch_bridge: ovs_bridge_{{ interface.bridge }}_present
     {%- endif %}
 
 ovs_port_set_type_{{ interface_name }}:
@@ -158,28 +220,16 @@
   - defaults:
       port: {{ interface|yaml }}
       port_name: {{ interface_name }}
+      auto: ""
+      iface_inet: ""
   - template: jinja
 
-ovs_port_{{ interface_name }}_line1:
-  file.replace:
-  - name: /etc/network/interfaces
-  - pattern: auto {{ interface_name }}$
-  - repl: ""
-
-ovs_port_{{ interface_name }}_line2:
-  file.replace:
-  - name: /etc/network/interfaces
-  - pattern: 'iface {{ interface_name }} inet .*'
-  - repl: ""
-
 ovs_port_up_{{ interface_name }}:
   cmd.run:
   - name: ifup {{ interface_name }}
   - require:
     - file: ovs_port_{{ interface_name }}
-    - file: ovs_port_{{ interface_name }}_line1
-    - file: ovs_port_{{ interface_name }}_line2
-    - openvswitch_bridge: ovs_bridge_{{ interface.bridge }}
+    - openvswitch_bridge: ovs_bridge_{{ interface.bridge }}_present
     - file: linux_interfaces_final_include
 
 {%- endif %}
@@ -222,6 +272,9 @@
   - wireless-psk: {{ interface.wireless.key }}
   {%- endif %}
   {%- endif %}
+  {%- if pillar.linux.network.noifupdown is defined %}
+  - noifupdown: {{ pillar.linux.network.noifupdown }}
+  {%- endif %}
   {%- for param in network.interface_params %}
   {{ set_param(param, interface) }}
   {%- endfor %}
diff --git a/linux/storage/swap.sls b/linux/storage/swap.sls
index 7b8d82e..3b3fd80 100644
--- a/linux/storage/swap.sls
+++ b/linux/storage/swap.sls
@@ -53,6 +53,24 @@
 
 {%- endif %}
 
+{%- else %}
+
+{{ swap.device }}:
+  module.run:
+    - name: mount.rm_fstab
+    - m_name: none
+    - device: {{ swap.device }}
+    - onlyif: grep -q {{ swap.device }} /etc/fstab
+
+linux_disable_swap_{{ swap.engine }}_{{ swap.device }}:
+  cmd.run:
+  {%- if swap.engine == 'partition' %}
+    - name: 'swapoff {{ swap.device }}'
+  {%- elif swap.engine == 'file' %}
+    - name: 'swapoff {{ swap.device }} && rm -f {{ swap.device }}'
+  {%- endif %}
+    - onlyif: file -L -s {{ swap.device }} | grep -q 'swap file'
+
 {%- endif %}
 
 {%- endfor %}
diff --git a/linux/system/at.sls b/linux/system/at.sls
index a441d1a..864ae0c 100644
--- a/linux/system/at.sls
+++ b/linux/system/at.sls
@@ -33,8 +33,8 @@
     - template: jinja
     - source: salt://linux/files/cron_users.jinja
     - user: root
-    - group: root
-    - mode: 0600
+    - group: daemon
+    - mode: 0640
     - defaults:
         users: {{ allow_users | yaml }}
     - require:
diff --git a/linux/system/auth.sls b/linux/system/auth.sls
index 2de2f6c..690ec04 100644
--- a/linux/system/auth.sls
+++ b/linux/system/auth.sls
@@ -1,9 +1,13 @@
 {%- from "linux/map.jinja" import auth with context %}
 
 {%- if auth.enabled %}
-  {%- set pam_modules_enable = "" %}
-  {%- set pam_modules_disable = "" %}
-  {%- if grains.os_family == 'Debian' %}
+  {%- if auth.duo.enabled %}
+include:
+  - linux.system.auth.duo
+  {%- else %}
+    {%- set pam_modules_enable = "" %}
+    {%- set pam_modules_disable = "" %}
+    {%- if grains.os_family == 'Debian' %}
 linux_auth_pam_packages:
   pkg.installed:
   - pkgs: [ 'libpam-runtime' ]
@@ -15,11 +19,11 @@
     - mode: 755
     - require:
       - pkg: linux_auth_pam_packages
-  {%- endif %}
+    {%- endif %}
 
-  {%- if auth.get('mkhomedir', {}).get('enabled', False) %}
-    {%- if grains.os_family == 'Debian' %}
-      {%- set pam_modules_enable = pam_modules_enable + ' mkhomedir' %}
+    {%- if auth.get('mkhomedir', {}).get('enabled', False) %}
+      {%- if grains.os_family == 'Debian' %}
+        {%- set pam_modules_enable = pam_modules_enable + ' mkhomedir' %}
 linux_auth_mkhomedir_debconf_package:
   pkg.installed:
   - pkgs: [ 'debconf-utils' ]
@@ -30,18 +34,18 @@
     - source: salt://linux/files/mkhomedir
     - template: jinja
 
+      {%- endif %}
+    {%- else %}
+      {%- if grains.os_family == 'Debian' %}
+        {%- set pam_modules_disable = pam_modules_disable + ' mkhomedir' %}
+      {%- endif %}
     {%- endif %}
-  {%- else %}
-    {%- if grains.os_family == 'Debian' %}
-      {%- set pam_modules_disable = pam_modules_disable + ' mkhomedir' %}
-    {%- endif %}
-  {%- endif %}
 
-  {%- if auth.get('ldap', {}).get('enabled', False) %}
-    {%- from "linux/map.jinja" import ldap with context %}
+    {%- if auth.get('ldap', {}).get('enabled', False) %}
+      {%- from "linux/map.jinja" import ldap with context %}
 
-    {%- if grains.os_family == 'Debian' %}
-      {%- set pam_modules_enable = pam_modules_enable + ' ldap' %}
+      {%- if grains.os_family == 'Debian' %}
+        {%- set pam_modules_enable = pam_modules_enable + ' ldap' %}
 
 linux_auth_ldap_debconf_package:
   pkg.installed:
@@ -69,16 +73,16 @@
         libpam-ldapd/enable_shadow:
           type: 'boolean'
           value: 'true'
+      {%- endif %}
+    {%- else %}
+      {%- if grains.os_family == 'Debian' %}
+        {%- set pam_modules_disable = pam_modules_disable + ' ldap' %}
+      {%- endif %}
     {%- endif %}
-  {%- else %}
-    {%- if grains.os_family == 'Debian' %}
-      {%- set pam_modules_disable = pam_modules_disable + ' ldap' %}
-    {%- endif %}
-  {%- endif %}
 
   {#- Setup PAM profiles #}
-  {%- if grains.os_family == 'Debian' %}
-    {%- if auth.get('mkhomedir', {}).get('enabled', False) %}
+    {%- if grains.os_family == 'Debian' %}
+      {%- if auth.get('mkhomedir', {}).get('enabled', False) %}
 linux_auth_pam_add_profiles_mkhomedir_enable:
   cmd.run:
     - name: /usr/local/bin/pam-add-profile {{ pam_modules_enable }}
@@ -92,19 +96,19 @@
       - file: linux_auth_mkhomedir_config
     - require:
       - file: linux_auth_pam_add_profile
-      {%- if auth.get('ldap', {}).get('enabled', False) %}
+        {%- if auth.get('ldap', {}).get('enabled', False) %}
       - pkg: linux_auth_ldap_packages
-      {%- endif %}
-    {%- else %}
+        {%- endif %}
+      {%- else %}
 linux_auth_pam_remove_profiles_mkhomedir:
   cmd.run:
     - name: /usr/sbin/pam-auth-update --remove {{ pam_modules_disable }}
     - onlyif: "[[ `grep -c pam_mkhomedir.so /etc/pam.d/common-session` -ne 0 ]]"
     - require:
       - pkg: linux_auth_pam_packages
-    {%- endif %}
+      {%- endif %}
 
-    {%- if auth.get('ldap', {}).get('enabled', False) %}
+      {%- if auth.get('ldap', {}).get('enabled', False) %}
 linux_auth_pam_add_profiles_ldap:
   cmd.run:
     - name: /usr/local/bin/pam-add-profile {{ pam_modules_enable }}
@@ -112,49 +116,49 @@
     - require:
       - file: linux_auth_pam_add_profile
       - pkg: linux_auth_ldap_packages
-    {%- else %}
+      {%- else %}
 linux_auth_pam_remove_profiles_ldap:
   cmd.run:
     - name: /usr/sbin/pam-auth-update --remove {{ pam_modules_disable }}
     - onlyif: "[[ `debconf-get-selections | grep libpam-runtime/profiles | grep -c ldap` -ne 0 ]]"
     - require:
       - pkg: linux_auth_pam_packages
-    {%- endif %}
+      {%- endif %}
 
-  {%- elif grains.os_family == 'RedHat' %}
-    {%- if auth.get('mkhomedir', {}).get('enabled', False) %}
+    {%- elif grains.os_family == 'RedHat' %}
+      {%- if auth.get('mkhomedir', {}).get('enabled', False) %}
 linux_auth_config_enable_mkhomedir:
   cmd.run:
     - name: "authconfig --enablemkhomedir --update"
     - require:
-      {%- if auth.get('ldap', {}).get('enabled', False) %}
+        {%- if auth.get('ldap', {}).get('enabled', False) %}
       - pkg: linux_auth_ldap_packages
-      {%- endif %}
-    {%- else %}
+        {%- endif %}
+      {%- else %}
 linux_auth_config_disable_mkhomedir:
   cmd.run:
     - name: "authconfig --disablemkhomedir --update"
     - require:
       - pkg: linux_auth_ldap_packages
-    {%- endif %}
-    {%- if auth.get('ldap', {}).get('enabled', False) %}
+      {%- endif %}
+      {%- if auth.get('ldap', {}).get('enabled', False) %}
 linux_auth_config_enable_ldap:
   cmd.run:
     - name: "authconfig --enableldap --enableldapauth --update"
     - require:
-      {%- if auth.get('ldap', {}).get('enabled', False) %}
+        {%- if auth.get('ldap', {}).get('enabled', False) %}
       - pkg: linux_auth_ldap_packages
-      {%- endif %}
-    {%- else %}
+        {%- endif %}
+      {%- else %}
 linux_auth_config_disable_ldap:
   cmd.run:
     - name: "authconfig --disableldap --disableldapauth --update"
     - require:
       - pkg: linux_auth_ldap_packages
+      {%- endif %}
     {%- endif %}
-  {%- endif %}
 
-  {%- if auth.get('ldap', {}).get('enabled', False) %}
+    {%- if auth.get('ldap', {}).get('enabled', False) %}
 
 linux_auth_nsswitch_config_file:
   file.managed:
@@ -187,6 +191,6 @@
   - enable: true
   - name: nslcd
 
+    {%- endif %}
   {%- endif %}
-
 {%- endif %}
diff --git a/linux/system/auth/duo.sls b/linux/system/auth/duo.sls
new file mode 100644
index 0000000..5270d8d
--- /dev/null
+++ b/linux/system/auth/duo.sls
@@ -0,0 +1,36 @@
+{%- if grains['os'] == 'Ubuntu' %}
+
+package_duo:
+  pkg.installed:
+    - name: duo-unix
+
+login_duo:
+  file.managed:
+    - name: /etc/duo/login_duo.conf
+    - source: salt://linux/files/login_duo.conf
+    - template: jinja
+    - user: 'root'
+    - group: 'root'
+    - mode: '0600'
+
+
+pam_duo:
+  file.managed:
+    - name: /etc/duo/pam_duo.conf
+    - source: salt://linux/files/login_duo.conf
+    - template: jinja
+    - user: 'root'
+    - group: 'root'
+    - mode: '0600'
+
+pam-sshd_config:
+  file.managed:
+  - name: /etc/pam.d/sshd
+  - user: root
+  - group: root
+  - source: salt://linux/files/pam-sshd
+  - mode: 600
+  - template: jinja
+
+{%- endif %}
+
diff --git a/linux/system/cron.sls b/linux/system/cron.sls
index 7f7ae0e..a5f57a4 100644
--- a/linux/system/cron.sls
+++ b/linux/system/cron.sls
@@ -33,8 +33,8 @@
     - template: jinja
     - source: salt://linux/files/cron_users.jinja
     - user: root
-    - group: root
-    - mode: 0600
+    - group: crontab
+    - mode: 0640
     - defaults:
         users: {{ allow_users | yaml }}
     - require:
diff --git a/linux/system/file.sls b/linux/system/file.sls
index ffc1d76..e8a6d52 100644
--- a/linux/system/file.sls
+++ b/linux/system/file.sls
@@ -21,6 +21,9 @@
     {%- else %}
     - skip_verify: True
     {%- endif %}
+    {%- if file.template is defined %}
+    - template: {{ file.template }}
+    {%- endif %}
     {%- elif file.contents is defined %}
     - contents: {{ file.contents|yaml }}
     {%- elif file.contents_pillar is defined %}
@@ -28,6 +31,7 @@
     {%- elif file.contents_grains is defined %}
     - contents_grains: {{ file.contents_grains }}
     {%- endif %}
+
 {%- endif %}
     {%- if file.name is defined %}
     - name: {{ file.name }}
diff --git a/linux/system/grub.sls b/linux/system/grub.sls
index 74ea553..49277ff 100644
--- a/linux/system/grub.sls
+++ b/linux/system/grub.sls
@@ -7,6 +7,7 @@
     - makedirs: True
 
 {%- if grains['os_family'] == 'RedHat' %}
+  {%- set boot_grub_cfg = '/boot/grub2/grub.cfg' %}
 /etc/default/grub:
   file.append:
     - text:
@@ -14,14 +15,26 @@
 
 grub_update:
   cmd.wait:
-  - name: grub2-mkconfig -o /boot/grub2/grub.cfg
+  - name: grub2-mkconfig -o {{ boot_grub_cfg }}
 
 {%- else %}
+  {%- set boot_grub_cfg = '/boot/grub/grub.cfg' %}
 
-{%- if grains.get('virtual_subtype', None) not in ['Docker', 'LXC'] %}
 grub_update:
   cmd.wait:
   - name: update-grub
-{%- endif %}
+  {%- if grains.get('virtual_subtype') in ['Docker', 'LXC'] %}
+  - onlyif: /bin/false
+  {%- endif %}
 
 {%- endif %}
+
+grub_cfg_permissions:
+  file.managed:
+    - name: {{ boot_grub_cfg }}
+    - user: 'root'
+    - owner: 'root'
+    - mode: '400'
+    - onlyif: test -f {{ boot_grub_cfg }}
+    - require:
+      - cmd: grub_update
diff --git a/linux/system/init.sls b/linux/system/init.sls
index 4f97fa0..20d39d9 100644
--- a/linux/system/init.sls
+++ b/linux/system/init.sls
@@ -3,6 +3,10 @@
 include:
 - linux.system.env
 - linux.system.profile
+- linux.system.shell
+{%- if system.login_defs is defined %}
+- linux.system.login_defs
+{%- endif %}
 - linux.system.at
 - linux.system.cron
 {%- if system.repo|length > 0 %}
diff --git a/linux/system/kernel.sls b/linux/system/kernel.sls
index e6111c5..3dc3046 100644
--- a/linux/system/kernel.sls
+++ b/linux/system/kernel.sls
@@ -8,10 +8,10 @@
 {%- do kernel_boot_opts.append('elevator=' ~ system.kernel.elevator) if system.kernel.elevator is defined %}
 {%- do kernel_boot_opts.extend(system.kernel.boot_options) if system.kernel.boot_options is defined %}
 
-{%- if kernel_boot_opts %}
 include:
   - linux.system.grub
 
+{%- if kernel_boot_opts %}
 /etc/default/grub.d/99-custom-settings.cfg:
   file.managed:
     - contents: 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT {{ kernel_boot_opts|join(' ') }}"'
diff --git a/linux/system/login_defs.sls b/linux/system/login_defs.sls
new file mode 100644
index 0000000..f94348a
--- /dev/null
+++ b/linux/system/login_defs.sls
@@ -0,0 +1,13 @@
+{%- from "linux/map.jinja" import system with context %}
+{%- if system.enabled %}
+  {%- if system.login_defs is defined %}
+login_defs:
+  file.managed:
+    - name: /etc/login.defs
+    - source: salt://linux/files/login.defs.jinja
+    - template: jinja
+    - user: root
+    - group: root
+    - mode: 644
+  {%- endif %}
+{%- endif %}
diff --git a/linux/system/repo.sls b/linux/system/repo.sls
index d000f50..03bb72d 100644
--- a/linux/system/repo.sls
+++ b/linux/system/repo.sls
@@ -62,7 +62,7 @@
   file.absent
       {%- endif %}
 
-      {%- if repo.pin is defined %}
+      {%- if repo.pin is defined or repo.pinning is defined %}
 linux_repo_{{ name }}_pin:
   file.managed:
     - name: /etc/apt/preferences.d/{{ name }}
diff --git a/linux/system/shell.sls b/linux/system/shell.sls
new file mode 100644
index 0000000..29fc1dc
--- /dev/null
+++ b/linux/system/shell.sls
@@ -0,0 +1,45 @@
+{%- from "linux/map.jinja" import system with context %}
+{%- if system.enabled %}
+  {%- if system.shell is defined %}
+
+    {%- if system.shell.umask is defined %}
+etc_bash_bashrc_umask:
+  file.blockreplace:
+    - name: /etc/bash.bashrc
+    - marker_start: "# BEGIN CIS 5.4.4 default user umask"
+    - marker_end: "# END CIS 5.4.4 default user umask"
+    - content: "umask {{ system.shell.umask }}"
+    - append_if_not_found: True
+    - onlyif: test -f /etc/bash.bashrc
+
+etc_profile_umask:
+  file.blockreplace:
+    - name: /etc/profile
+    - marker_start: "# BEGIN CIS 5.4.4 default user umask"
+    - marker_end: "# END CIS 5.4.4 default user umask"
+    - content: "umask {{ system.shell.umask }}"
+    - append_if_not_found: True
+    - onlyif: test -f /etc/profile
+    {%- endif %}
+
+    {%- if system.shell.timeout is defined %}
+etc_bash_bashrc_timeout:
+  file.blockreplace:
+    - name: /etc/bash.bashrc
+    - marker_start: "# BEGIN CIS 5.4.5 default user shell timeout"
+    - marker_end: "# END CIS 5.4.5 default user shell timeout"
+    - content: "TMOUT={{ system.shell.timeout }}"
+    - append_if_not_found: True
+    - onlyif: test -f /etc/bash.bashrc
+
+etc_profile_timeout:
+  file.blockreplace:
+    - name: /etc/profile
+    - marker_start: "# BEGIN CIS 5.4.5 default user shell timeout"
+    - marker_end: "# END CIS 5.4.5 default user shell timeout"
+    - content: "TMOUT={{ system.shell.timeout }}"
+    - append_if_not_found: True
+    - onlyif: test -f /etc/profile
+    {%- endif %}
+  {%- endif %}
+{%- endif %}
diff --git a/linux/system/sysfs.sls b/linux/system/sysfs.sls
index 8440384..a4e28bf 100644
--- a/linux/system/sysfs.sls
+++ b/linux/system/sysfs.sls
@@ -11,6 +11,8 @@
     - require:
       - pkg: linux_sysfs_package
 
+{% set apply = system.get('sysfs', {}).pop('enable_apply', True) %}
+
 {%- for name, sysfs in system.get('sysfs', {}).items() %}
 
 /etc/sysfs.d/{{ name }}.conf:
@@ -32,6 +34,8 @@
 {%- set sysfs_list = sysfs %}
 {%- endif %}
 
+{%- if apply %}
+
 {%- for item in sysfs_list %}
 {%- set list_idx = loop.index %}
 {%- for key, value in item.items() %}
@@ -48,4 +52,7 @@
   {%- endfor %}
 
 {%- endfor %}
+
+{%- endif %}
+
 {%- endfor %}
diff --git a/linux/system/timezone.sls b/linux/system/timezone.sls
index 6b8e778..f7076c5 100644
--- a/linux/system/timezone.sls
+++ b/linux/system/timezone.sls
@@ -5,8 +5,11 @@
 
 {{ system.timezone }}:
   timezone.system:
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
   - utc: {{ system.utc }}
 
 {%- endif %}
 
-{%- endif %}
\ No newline at end of file
+{%- endif %}
diff --git a/linux/system/user.sls b/linux/system/user.sls
index 7a0c98b..42086d5 100644
--- a/linux/system/user.sls
+++ b/linux/system/user.sls
@@ -37,7 +37,11 @@
   - password: {{ user.password }}
   - hash_password: {{ user.get('hash_password', False) }}
   {% endif %}
+  {%- if user.gid is defined and user.gid %}
+  - gid: {{ user.gid }}
+  {%- else %}
   - gid_from_name: true
+  {%- endif %}
   {%- if user.groups is defined %}
   - groups: {{ user.groups }}
   {%- endif %}
@@ -50,6 +54,21 @@
   {%- if user.uid is defined and user.uid %}
   - uid: {{ user.uid }}
   {%- endif %}
+  {%- if user.unique is defined %}
+  - unique: {{ user.unique }}
+  {%- endif %}
+  {%- if user.maxdays is defined %}
+  - maxdays: {{ user.maxdays }}
+  {%- endif %}
+  {%- if user.mindays is defined %}
+  - mindays: {{ user.mindays }}
+  {%- endif %}
+  {%- if user.warndays is defined %}
+  - warndays: {{ user.warndays }}
+  {%- endif %}
+  {%- if user.inactdays is defined %}
+  - inactdays: {{ user.inactdays }}
+  {%- endif %}
   - require: {{ requires|yaml }}
 
 system_user_home_{{ user.home }}:
diff --git a/metadata/service/system/cis/cis-1-1-1-1.yml b/metadata/service/system/cis/cis-1-1-1-1.yml
new file mode 100644
index 0000000..2331a54
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-1.yml
@@ -0,0 +1,37 @@
+# 1.1.1.1 Ensure mounting of cramfs filesystems is disabled
+#
+# Description
+# ===========
+# The cramfs filesystem type is a compressed read-only Linux filesystem
+# embedded in small footprint systems. A cramfs image can be used without
+# having to first decompress the image.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the server. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v cramfs
+#   install /bin/true
+#   # lsmod | grep cramfs
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install cramfs /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          cramfs:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-1-2.yml b/metadata/service/system/cis/cis-1-1-1-2.yml
new file mode 100644
index 0000000..f84b56f
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-2.yml
@@ -0,0 +1,36 @@
+# 1.1.1.2 Ensure mounting of freevxfs filesystems is disabled
+#
+# Description
+# ===========
+# The freevxfs filesystem type is a free version of the Veritas type
+# filesystem. This is the primary filesystem type for HP-UX operating systems.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the system. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v freevxfs
+#   install /bin/true
+#   # lsmod | grep freevxfs
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install freevxfs /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          freevxfs:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-1-3.yml b/metadata/service/system/cis/cis-1-1-1-3.yml
new file mode 100644
index 0000000..91390b5
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-3.yml
@@ -0,0 +1,36 @@
+# 1.1.1.3 Ensure mounting of jffs2 filesystems is disabled
+#
+# Description
+# ===========
+# The jffs2 (journaling flash filesystem 2) filesystem type is a
+# log-structured filesystem used in flash memory devices.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the system. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v jffs2
+#   install /bin/true
+#   # lsmod | grep jffs2
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install jffs2 /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          jffs2:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-1-4.yml b/metadata/service/system/cis/cis-1-1-1-4.yml
new file mode 100644
index 0000000..c246ad2
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-4.yml
@@ -0,0 +1,36 @@
+# 1.1.1.4 Ensure mounting of hfs filesystems is disabled
+#
+# Description
+# ===========
+# The hfs filesystem type is a hierarchical filesystem that allows
+# you to mount Mac OS filesystems.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the system. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v hfs
+#   install /bin/true
+#   # lsmod | grep hfs
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install hfs /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          hfs:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-1-5.yml b/metadata/service/system/cis/cis-1-1-1-5.yml
new file mode 100644
index 0000000..e258052
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-5.yml
@@ -0,0 +1,36 @@
+# 1.1.1.5 Ensure mounting of hfsplus filesystems is disabled
+#
+# Description
+# ===========
+# The hfsplus filesystem type is a hierarchical filesystem designed to
+# replace hfs that allows you to mount Mac OS filesystems.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the system. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v hfsplus
+#   install /bin/true
+#   # lsmod | grep hfsplus
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install hfsplus /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          hfsplus:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-1-6.yml b/metadata/service/system/cis/cis-1-1-1-6.yml
new file mode 100644
index 0000000..59da5db
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-6.yml
@@ -0,0 +1,43 @@
+# 1.1.1.6 Ensure mounting of squashfs filesystems is disabled
+#
+# Description
+# ===========
+# The squashfs filesystem type is a compressed read-only Linux filesystem
+# embedded in small footprint systems (similar to cramfs). A squashfs image
+# can be used without having to first decompress the image.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the server. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v squashfs
+#   install /bin/true
+#   # lsmod | grep squashfs
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install squashfs /bin/true
+#
+# NOTE
+# ====
+# In Ubuntu 16.04 squashfs is built into kernel, and 'install' command
+# from modprobe.d dir has no effect. However, this is still checked by
+# CIS-CAT in Ubuntu 16.04 benchmark v.1.0.0. This was removed in v.1.1.0.
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          squashfs:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-1-7.yml b/metadata/service/system/cis/cis-1-1-1-7.yml
new file mode 100644
index 0000000..0102220
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-7.yml
@@ -0,0 +1,38 @@
+# 1.1.1.7 Ensure mounting of udf filesystems is disabled
+#
+# Description
+# ===========
+# The udf filesystem type is the universal disk format used to implement
+# ISO/IEC 13346 and ECMA-167 specifications. This is an open vendor filesystem
+# type for data storage on a broad range of media. This filesystem type is
+# necessary to support writing DVDs and newer optical disc formats.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the server. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v udf
+#   install /bin/true
+#   # lsmod | grep udf
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install udf /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          udf:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-1-8.yml b/metadata/service/system/cis/cis-1-1-1-8.yml
new file mode 100644
index 0000000..7c06c8e
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-1-8.yml
@@ -0,0 +1,50 @@
+# 1.1.1.8 Ensure mounting of FAT filesystems is disabled
+#
+# Description
+# ===========
+# The FAT filesystem format is primarily used on older windows systems and
+# portable USB drives or flash modules. It comes in three types FAT12, FAT16,
+# and FAT32 all of which are supported by the vfat kernel module.
+#
+# Rationale
+# =========
+# Removing support for unneeded filesystem types reduces the local attack
+# surface of the server. If this filesystem type is not needed, disable it.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v vfat
+#   install /bin/true
+#   # lsmod | grep vfat
+#   <No output>
+#
+# Remediation
+# ===========
+#
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install vfat /bin/true
+#
+# Impact
+# ======
+# FAT filesystems are often used on portable USB sticks and other flash
+# media are commonly used to transfer files between workstations, removing
+# VFAT support may prevent the ability to transfer files in this way.
+#
+# NOTE
+# ====
+# In Ubuntu 16.04 vfat is built into kernel, and 'install' command
+# from modprobe.d dir has no effect. However, this is still checked by
+# CIS-CAT in Ubuntu 16.04 benchmark v.1.0.0. This was removed in v.1.1.0.
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          vfat:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-1-1-14_15_16.yml b/metadata/service/system/cis/cis-1-1-14_15_16.yml
new file mode 100644
index 0000000..d9c7e72
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-14_15_16.yml
@@ -0,0 +1,95 @@
+# CIS 1.1.14 Ensure nodev option set on /dev/shm partition (Scored)
+#
+# Description
+# ===========
+# The nodev mount option specifies that the filesystem cannot contain special
+# devices.
+#
+# Rationale
+# =========
+# Since the /run/shm filesystem is not intended to support devices, set this
+# option to ensure that users cannot attempt to create special devices in
+# /dev/shm partitions.
+#
+# Audit
+# =====
+# Run the following command and verify that the nodev option is set on /dev/shm .
+#
+#   # mount | grep /dev/shm
+#   shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime)
+#
+# Remediation
+# ===========
+#
+# Edit the /etc/fstab file and add nodev to the fourth field (mounting options)
+# for the /dev/shm partition. See the fstab(5) manual page for more information.
+# Run the following command to remount /dev/shm :
+#
+#   # mount -o remount,nodev /dev/shm
+#
+# CIS 1.1.15 Ensure nosuid option set on /dev/shm partition (Scored)
+#
+# Description
+# ===========
+# The nosuid mount option specifies that the filesystem cannot contain setuid
+# files.
+#
+# Rationale
+# =========
+# Setting this option on a file system prevents users from introducing
+# privileged programs onto the system and allowing non-root users to execute them.
+#
+# Audit
+# =====
+# Run the following command and verify that the no suid option is set on /dev/shm .
+#
+#   # mount | grep /dev/shm
+#   shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime)
+#
+# Remediation
+# ===========
+# Edit the /etc/fstab file and add nosuid to the fourth field (mounting options)
+# for the /dev/shm partition. See the fstab(5) manual page for more information.
+# Run the following command to remount /dev/shm :
+#
+#   # mount -o remount,nosuid /dev/shm
+#
+# 1.1.16 Ensure noexec option set on /dev/shm partition (Scored)
+#
+# Description
+# ===========
+# The noexec mount option specifies that the filesystem cannot contain
+# executable binaries.
+#
+# Rationale
+# =========
+# Setting this option on a file system prevents users from executing programs
+# from shared memory. This deters users from introducing potentially malicious
+# software on the system.
+#
+# Audit
+# =====
+# Run the following command and verify that the noexec option is set on /run/shm .
+#
+#   # mount | grep /dev/shm
+#   shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime)
+#
+# Remediation
+# ===========
+# Edit the /etc/fstab file and add noexec to the fourth field (mounting options)
+# for the /dev/shm partition. See the fstab(5) manual page for more information.
+# Run the following command to remount /dev/shm :
+#
+#   # mount -o remount,noexec /dev/shm
+#
+parameters:
+  linux:
+    storage:
+      mount:
+        ensure_dev_shm_mount_options:
+          enabled: true
+          file_system: tmpfs
+          device: shm
+          path: /dev/shm
+          opts: rw,nosuid,nodev,noexec,relatime
+
diff --git a/metadata/service/system/cis/cis-1-1-21.yml b/metadata/service/system/cis/cis-1-1-21.yml
new file mode 100644
index 0000000..da84f49
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-1-21.yml
@@ -0,0 +1,53 @@
+# CIS 1.1.21 Disable Automounting
+#
+# Description
+# ===========
+# autofs allows automatic mounting of devices, typically including CD/DVDs
+# and USB drives.
+#
+# Rationale
+# =========
+# With automounting enabled anyone with physical access could attach a USB
+# drive or disc and have its contents available in system even if they lacked
+# permissions to mount it themselves.
+#
+# Audit
+# =====
+# Run the following command to verify autofs is not enabled:
+#
+#   # systemctl is-enabled autofs
+#   disabled
+#
+# Verify result is not "enabled".
+#
+# Remediation
+# ===========
+#
+# Run the following command to disable autofs :
+#
+#   # systemctl disable autofs
+#
+# Impact
+# ======
+# The use portable hard drives is very common for workstation users. If your
+# organization allows the use of portable storage or media on workstations
+# and physical access controls to workstations is considered adequate there
+# is little value add in turning off automounting.
+#
+# Notes
+# =====
+# This control should align with the tolerance of the use of portable drives
+# and optical media in the organization. On a server requiring an admin to
+# manually mount media can be part of defense-in-depth to reduce the risk of
+# unapproved software or information being introduced or proprietary software
+# or information being exfiltrated. If admins commonly use flash drives and
+# Server access has sufficient physical controls, requiring manual mounting
+# may not increase security.
+#
+parameters:
+  linux:
+    system:
+      service:
+        autofs:
+          status: disabled
+
diff --git a/metadata/service/system/cis/cis-1-5-4.yml b/metadata/service/system/cis/cis-1-5-4.yml
new file mode 100644
index 0000000..5583d80
--- /dev/null
+++ b/metadata/service/system/cis/cis-1-5-4.yml
@@ -0,0 +1,37 @@
+# CIS 1.5.4 Ensure prelink is disabled
+#
+# Description
+# ===========
+# prelink is a program that modifies ELF shared libraries and ELF dynamically
+# linked binaries in such a way that the time needed for the dynamic linker to
+# perform relocations at startup significantly decreases.
+#
+# Rationale
+# =========
+# The prelinking feature can interfere with the operation of AIDE, because it
+# changes binaries. Prelinking can also increase the vulnerability of the system
+# if a malicious user is able to compromise a common library such as libc.
+#
+# Audit
+# =====
+# Run the following command and verify prelink is not installed:
+#
+#   # dpkg -s prelink
+#
+# Remediation
+# ===========
+# Run the following command to restore binaries to normal:
+#
+#   # prelink -ua
+#
+# Run the following command to uninstall prelink :
+#
+#   # apt-get remove prelink
+#
+parameters:
+  linux:
+    system:
+      package:
+        prelink:
+          version: removed
+
diff --git a/metadata/service/system/cis/cis-2-3-1.yml b/metadata/service/system/cis/cis-2-3-1.yml
new file mode 100644
index 0000000..6116f36
--- /dev/null
+++ b/metadata/service/system/cis/cis-2-3-1.yml
@@ -0,0 +1,43 @@
+# 2.3.1 Ensure NIS Client is not installed
+#
+# Description
+# ===========
+# The Network Information Service (NIS), formerly known as Yellow Pages,
+# is a client-server directory service protocol used to distribute system
+# configuration files. The NIS client ( ypbind ) was used to bind a machine
+# to an NIS server and receive the distributed configuration files.
+#
+# Rationale
+# =========
+# The NIS service is inherently an insecure system that has been vulnerable
+# to DOS attacks, buffer overflows and has poor authentication for querying
+# NIS maps. NIS generally has been replaced by such protocols as Lightweight
+# Directory Access Protocol (LDAP). It is recommended that the service be
+# removed.
+#
+# Audit
+# =====
+# Run the following command and verify nis is not installed:
+#
+#   dpkg -s nis
+#
+# Remediation
+# ===========
+# Run the following command to uninstall nis:
+#
+#   apt-get remove nis
+#
+# Impact
+# ======
+# Many insecure service clients are used as troubleshooting tools and in
+# testing environments. Uninstalling them can inhibit capability to test
+# and troubleshoot. If they are required it is advisable to remove the clients
+# after use to prevent accidental or intentional misuse.
+#
+parameters:
+  linux:
+    system:
+      package:
+        nis:
+          version: removed
+
diff --git a/metadata/service/system/cis/cis-2-3-2.yml b/metadata/service/system/cis/cis-2-3-2.yml
new file mode 100644
index 0000000..ecbfa6a
--- /dev/null
+++ b/metadata/service/system/cis/cis-2-3-2.yml
@@ -0,0 +1,55 @@
+# 2.3.2 Ensure rsh client is not installed
+#
+# Description
+# ===========
+# The rsh package contains the client commands for the rsh services.
+#
+# Rationale
+# =========
+# These legacy clients contain numerous security exposures and have been
+# replaced with the more secure SSH package. Even if the server is removed,
+# it is best to ensure the clients are also removed to prevent users from
+# inadvertently attempting to use these commands and therefore exposing
+# their credentials. Note that removing the rsh package removes the
+# clients for rsh , rcp and rlogin .
+#
+# Audit
+# =====
+# Run the following commands and verify rsh is not installed:
+#
+#   dpkg -s rsh-client
+#   dpkg -s rsh-redone-client
+#
+# Remediation
+# ===========
+# Run the following command to uninstall rsh :
+#
+#   apt-get remove rsh-client rsh-redone-client
+#
+# Impact
+# ======
+# Many insecure service clients are used as troubleshooting tools and in
+# testing environments. Uninstalling them can inhibit capability to test
+# and troubleshoot. If they are required it is advisable to remove the
+# clients after use to prevent accidental or intentional misuse.
+#
+# NOTE
+# ====
+# It is not possible to remove rsh-client by means of SaltStack because
+# of the way SaltStack checks that package was really removed. 'rsh-client'
+# is "provided" by openssh-client package, and SaltStack thinks that
+# it is the same as 'rsh-client is installed'. So each time we try to
+# remove 'rsh-client' on a system where 'openssh-client' is installed
+# (that's almost every system), we got state failure.
+# This was fixed in upstream SaltStack in 2018, not sure where we start using
+# this version. Until that moment 'rsh-client' should remain unmanaged.
+#
+parameters:
+  linux:
+    system:
+      package:
+#        rsh-client:
+#          version: removed
+        rsh-redone-client:
+          version: removed
+
diff --git a/metadata/service/system/cis/cis-2-3-3.yml b/metadata/service/system/cis/cis-2-3-3.yml
new file mode 100644
index 0000000..859754b
--- /dev/null
+++ b/metadata/service/system/cis/cis-2-3-3.yml
@@ -0,0 +1,39 @@
+# 2.3.3 Ensure talk client is not installed
+#
+# Description
+# ===========
+# The talk software makes it possible for users to send and receive messages
+# across systems through a terminal session. The talk client, which allows
+# initialization of talk sessions, is installed by default.
+#
+# Rationale
+# =========
+# The software presents a security risk as it uses unencrypted protocols
+# for communication.
+#
+# Audit
+# =====
+# Run the following command and verify talk is not installed:
+#
+#   dpkg -s talk
+#
+# Remediation
+# ===========
+# Run the following command to uninstall talk :
+#
+#   apt-get remove talk
+#
+# Impact
+# ======
+# Many insecure service clients are used as troubleshooting tools and in
+# testing environments. Uninstalling them can inhibit capability to test
+# and troubleshoot. If they are required it is advisable to remove the clients
+# after use to prevent accidental or intentional misuse.
+#
+parameters:
+  linux:
+    system:
+      package:
+        talk:
+          version: removed
+
diff --git a/metadata/service/system/cis/cis-2-3-4.yml b/metadata/service/system/cis/cis-2-3-4.yml
new file mode 100644
index 0000000..34c8eb2
--- /dev/null
+++ b/metadata/service/system/cis/cis-2-3-4.yml
@@ -0,0 +1,40 @@
+# 2.3.4 Ensure telnet client is not installed
+#
+# Description
+# ===========
+# The telnet package contains the telnet client, which allows users to start
+# connections to other systems via the telnet protocol.
+#
+# Rationale
+# =========
+# The telnet protocol is insecure and unencrypted. The use of an unencrypted
+# transmission medium could allow an unauthorized user to steal credentials.
+# The ssh package provides an encrypted session and stronger security and is
+# included in most Linux distributions.
+#
+# Audit
+# =====
+# Run the following command and verify telnet is not installed:
+#
+#   # dpkg -s telnet
+#
+# Remediation
+# ===========
+# Run the following command to uninstall telnet :
+#
+#   # apt-get remove telnet
+#
+# Impact
+# ======
+# Many insecure service clients are used as troubleshooting tools and in
+# testing environments. Uninstalling them can inhibit capability to test and
+# troubleshoot. If they are required it is advisable to remove the clients
+# after use to prevent accidental or intentional misuse.
+#
+parameters:
+  linux:
+    system:
+      package:
+        telnet:
+          version: removed
+
diff --git a/metadata/service/system/cis/cis-3-5-1.yml b/metadata/service/system/cis/cis-3-5-1.yml
new file mode 100644
index 0000000..b232990
--- /dev/null
+++ b/metadata/service/system/cis/cis-3-5-1.yml
@@ -0,0 +1,38 @@
+# 3.5.2 Ensure DCCP is disabled
+#
+# Description
+# ===========
+# The Datagram Congestion Control Protocol (DCCP) is a transport layer protocol
+# that supports streaming media and telephony. DCCP provides a way to gain
+# access to congestion control, without having to do it at the application
+# layer, but does not provide in-sequence delivery.
+#
+# Rationale
+# =========
+# If the protocol is not required, it is recommended that the drivers not be
+# installed to reduce the potential attack surface.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v dccp
+#   install /bin/true
+#   # lsmod | grep dccp
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install dccp /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          dccp:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-3-5-2.yml b/metadata/service/system/cis/cis-3-5-2.yml
new file mode 100644
index 0000000..0207eb9
--- /dev/null
+++ b/metadata/service/system/cis/cis-3-5-2.yml
@@ -0,0 +1,41 @@
+# 3.5.2 Ensure SCTP is disabled
+#
+# Description
+# ===========
+# The Stream Control Transmission Protocol (SCTP) is a transport layer
+# protocol used to support message oriented communication, with several
+# streams of messages in one connection. It serves a similar function as
+# TCP and UDP, incorporating features of both. It is message-oriented
+# like UDP, and ensures reliable in-sequence transport of messages with
+# congestion control like TCP.
+#
+# Rationale
+# =========
+# If the protocol is not being used, it is recommended that kernel module
+# not be loaded, disabling the service to reduce the potential attack surface.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v sctp
+#   install /bin/true
+#   # lsmod | grep sctp
+#   <No output>
+#
+# Remediation
+# ===========
+#
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install sctp /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          sctp:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-3-5-3.yml b/metadata/service/system/cis/cis-3-5-3.yml
new file mode 100644
index 0000000..723de8b
--- /dev/null
+++ b/metadata/service/system/cis/cis-3-5-3.yml
@@ -0,0 +1,37 @@
+# 3.5.3 Ensure RDS is disabled
+#
+# Description
+# ===========
+# The Reliable Datagram Sockets (RDS) protocol is a transport layer protocol
+# designed to provide low-latency, high-bandwidth communications between
+# cluster nodes. It was developed by the Oracle Corporation.
+#
+# Rationale
+# =========
+# If the protocol is not being used, it is recommended that kernel module
+# not be loaded, disabling the service to reduce the potential attack surface.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v rds
+#   install /bin/true
+#   # lsmod | grep rds
+#   <No output>
+#
+# Remediation
+# ===========
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install rds /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          rds:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-3-5-4.yml b/metadata/service/system/cis/cis-3-5-4.yml
new file mode 100644
index 0000000..6a4920c
--- /dev/null
+++ b/metadata/service/system/cis/cis-3-5-4.yml
@@ -0,0 +1,37 @@
+# 3.5.4 Ensure TIPC is disabled
+#
+# Description
+# ===========
+# The Transparent Inter-Process Communication (TIPC) protocol is designed
+# to provide communication between cluster nodes.
+#
+# Rationale
+# =========
+# If the protocol is not being used, it is recommended that kernel module
+# not be loaded, disabling the service to reduce the potential attack surface.
+#
+# Audit
+# =====
+# Run the following commands and verify the output is as indicated:
+#
+#   # modprobe -n -v tipc
+#   install /bin/true
+#   # lsmod | grep tipc
+#   <No output>
+#
+# Remediation
+# ===========
+#
+# Edit or create the file /etc/modprobe.d/CIS.conf and add the following line:
+#
+#   install tipc /bin/true
+#
+parameters:
+  linux:
+    system:
+      kernel:
+        module:
+          tipc:
+            install:
+              command: /bin/true
+
diff --git a/metadata/service/system/cis/cis-5-4-1-1.yml b/metadata/service/system/cis/cis-5-4-1-1.yml
new file mode 100644
index 0000000..8b82466
--- /dev/null
+++ b/metadata/service/system/cis/cis-5-4-1-1.yml
@@ -0,0 +1,52 @@
+# CIS 5.4.1.1 Ensure password expiration is 90 days or less (Scored)
+#
+# Description
+# ===========
+# The PASS_MAX_DAYS parameter in /etc/login.defs allows an administrator to
+# force passwords to expire once they reach a defined age. It is recommended
+# that the PASS_MAX_DAYS parameter be set to less than or equal to 90 days.
+#
+# Rationale
+# =========
+# The window of opportunity for an attacker to leverage compromised credentials
+# or successfully compromise credentials via an online brute force attack is
+# limited by the age of the password. Therefore, reducing the maximum age of a
+# password also reduces an attacker's window of opportunity.
+#
+# Audit
+# =====
+# Run the following command and verify PASS_MAX_DAYS is 90 or less:
+#
+#   # grep PASS_MAX_DAYS /etc/login.defs
+#   PASS_MAX_DAYS 90
+#
+# Verify all users with a password have their maximum days between password
+# change set to 90 or less:
+#
+#   # egrep ^[^:]+:[^\!*] /etc/shadow | cut -d: -f1
+#   <list of users>
+#   # chage --list <user>
+#   Maximum number of days between password change: 90
+#
+# Remediation
+# ===========
+# Set the PASS_MAX_DAYS parameter to 90 in /etc/login.defs :
+#
+#   PASS_MAX_DAYS 90
+#
+# Modify user parameters for all users with a password set to match:
+#
+#   # chage --maxdays 90 <user>
+#
+# Notes
+# =====
+# You can also check this setting in /etc/shadow directly. The 5th field
+# should be 90 or less for all users with a password.
+#
+parameters:
+  linux:
+    system:
+      login_defs:
+        PASS_MAX_DAYS:
+          value: 90
+
diff --git a/metadata/service/system/cis/cis-5-4-1-2.yml b/metadata/service/system/cis/cis-5-4-1-2.yml
new file mode 100644
index 0000000..50543ca
--- /dev/null
+++ b/metadata/service/system/cis/cis-5-4-1-2.yml
@@ -0,0 +1,52 @@
+# CIS 5.4.1.2 Ensure minimum days between password changes is 7 or more (Scored)
+#
+# Description
+# ===========
+# The PASS_MIN_DAYS parameter in /etc/login.defs allows an administrator to
+# prevent users from changing their password until a minimum number of days
+# have passed since the last time the user changed their password. It is
+# recommended that PASS_MIN_DAYS parameter be set to 7 or more days.
+#
+# Rationale
+# =========
+# By restricting the frequency of password changes, an administrator can
+# prevent users from repeatedly changing their password in an attempt to
+# circumvent password reuse controls.
+#
+# Audit
+# =====
+# Run the following command and verify PASS_MIN_DAYS is 7 or more:
+#
+#   # grep PASS_MIN_DAYS /etc/login.defs
+#   PASS_MIN_DAYS 7
+#
+# Verify all users with a password have their minimum days between password
+# change set to 7 or more:
+#
+#   # egrep ^[^:]+:[^\!*] /etc/shadow | cut -d: -f1
+#   <list of users>
+#   # chage --list <user>
+#   Minimum number of days between password change: 7
+#
+# Remediation
+# ===========
+# Set the PASS_MIN_DAYS parameter to 7 in /etc/login.defs :
+#
+#   PASS_MIN_DAYS 7
+#
+# Modify user parameters for all users with a password set to match:
+#
+#   # chage --mindays 7 <user>
+#
+# Notes
+# =====
+# You can also check this setting in /etc/shadow directly. The 5th field
+# should be 7 or more for all users with a password.
+#
+parameters:
+  linux:
+    system:
+      login_defs:
+        PASS_MIN_DAYS:
+          value: 7
+
diff --git a/metadata/service/system/cis/cis-5-4-1-3.yml b/metadata/service/system/cis/cis-5-4-1-3.yml
new file mode 100644
index 0000000..3567f2a
--- /dev/null
+++ b/metadata/service/system/cis/cis-5-4-1-3.yml
@@ -0,0 +1,52 @@
+# CIS 5.4.1.3 Ensure password expiration warning days is 7 or more (Scored)
+#
+# Description
+# ===========
+# The PASS_WARN_AGE parameter in /etc/login.defs allows an administrator to
+# notify users that their password will expire in a defined number of days.
+# It is recommended that the PASS_WARN_AGE parameter be set to 7 or more days.
+#
+# Rationale
+# =========
+# Providing an advance warning that a password will be expiring gives users
+# time to think of a secure password. Users caught unaware may choose a simple
+# password or write it down where it may be discovered.
+#
+# Audit
+# =====
+# Run the following command and verify PASS_WARN_AGE is 7 or more:
+#
+#   # grep PASS_WARN_AGE /etc/login.defs
+#   PASS_WARN_AGE 7
+#
+# Verify all users with a password have their number of days of warning before
+# password expires set to 7 or more:
+#
+#   # egrep ^[^:]+:[^\!*] /etc/shadow | cut -d: -f1
+#   <list of users>
+#   # chage --list <user>
+#   Number of days of warning before password expires: 7
+#
+# Remediation
+# ===========
+#
+# Set the PASS_WARN_AGE parameter to 7 in /etc/login.defs :
+#
+#   PASS_WARN_AGE 7
+#
+# Modify user parameters for all users with a password set to match:
+#
+#   # chage --warndays 7 <user>
+#
+# Notes
+# =====
+# You can also check this setting in /etc/shadow directly. The 6th field
+# should be 7 or more for all users with a password.
+#
+parameters:
+  linux:
+    system:
+      login_defs:
+        PASS_WARN_AGE:
+          value: 7
+
diff --git a/metadata/service/system/cis/cis-5-4-4.yml b/metadata/service/system/cis/cis-5-4-4.yml
new file mode 100644
index 0000000..639babc
--- /dev/null
+++ b/metadata/service/system/cis/cis-5-4-4.yml
@@ -0,0 +1,57 @@
+# CIS 5.4.4 Ensure default user umask is 027 or more restrictive (Scored)
+#
+# Description
+# ===========
+# The default umask determines the permissions of files created by users.
+# The user creating the file has the discretion of making their files and
+# directories readable by others via the chmod command. Users who wish to
+# allow their files and directories to be readable by others by default may
+# choose a different default umask by inserting the umask command into the
+# standard shell configuration files ( .profile , .bashrc , etc.) in their
+# home directories.
+#
+# Rationale
+# =========
+# Setting a very secure default value for umask ensures that users make a
+# conscious choice about their file permissions. A default umask setting of
+# 077 causes files and directories created by users to not be readable by
+# any other user on the system. A umask of 027 would make files and
+# directories readable by users in the same Unix group, while a umask of 022
+# would make files readable by every user on the system.
+#
+# Audit
+# =====
+# Run the following commands and verify all umask lines returned are 027 or
+# more restrictive.
+#
+#   # grep "^umask" /etc/bash.bashrc
+#   umask 027
+#   # grep "^umask" /etc/profile
+#   umask 027
+#
+# Remediation
+# ===========
+# Edit the /etc/bash.bashrc and /etc/profile files (and the appropriate files
+# for any other shell supported on your system) and add or edit any umask
+# parameters as follows:
+#
+#   umask 027
+#
+# Notes
+# =====
+# The audit and remediation in this recommendation apply to bash and shell.
+# If other shells are supported on the system, it is recommended that their
+# configuration files also are checked.
+#
+# Other methods of setting a default user umask exist however the shell
+# configuration files are the last run and will override other settings if
+# they exist therefore our recommendation is to configure in the shell
+# configuration files. If other methods are in use in your environment they
+# should be audited and the shell configs should be verified to not override.
+#
+parameters:
+  linux:
+    system:
+      shell:
+        umask: "027"
+
diff --git a/metadata/service/system/cis/init.yml b/metadata/service/system/cis/init.yml
index 5c91125..0c2626d 100644
--- a/metadata/service/system/cis/init.yml
+++ b/metadata/service/system/cis/init.yml
@@ -1,6 +1,21 @@
 classes:
+- service.linux.system.cis.cis-1-1-1-1
+- service.linux.system.cis.cis-1-1-1-2
+- service.linux.system.cis.cis-1-1-1-3
+- service.linux.system.cis.cis-1-1-1-4
+- service.linux.system.cis.cis-1-1-1-5
+- service.linux.system.cis.cis-1-1-1-6
+- service.linux.system.cis.cis-1-1-1-7
+- service.linux.system.cis.cis-1-1-1-8
+- service.linux.system.cis.cis-1-1-14_15_16
+- service.linux.system.cis.cis-1-1-21
 - service.linux.system.cis.cis-1-5-1
 - service.linux.system.cis.cis-1-5-3
+- service.linux.system.cis.cis-1-5-4
+- service.linux.system.cis.cis-2-3-1
+- service.linux.system.cis.cis-2-3-2
+- service.linux.system.cis.cis-2-3-3
+- service.linux.system.cis.cis-2-3-4
 - service.linux.system.cis.cis-3-1-2
 - service.linux.system.cis.cis-3-2-1
 - service.linux.system.cis.cis-3-2-2
@@ -12,6 +27,14 @@
 - service.linux.system.cis.cis-3-2-8
 # Temp. disable PROD-22520
 #- service.linux.system.cis.cis-3-3-3
+- service.linux.system.cis.cis-3-5-1
+- service.linux.system.cis.cis-3-5-2
+- service.linux.system.cis.cis-3-5-3
+- service.linux.system.cis.cis-3-5-4
+- service.linux.system.cis.cis-5-4-1-1
+- service.linux.system.cis.cis-5-4-1-2
+- service.linux.system.cis.cis-5-4-1-3
+- service.linux.system.cis.cis-5-4-4
 - service.linux.system.cis.cis-6-1-2
 - service.linux.system.cis.cis-6-1-3
 - service.linux.system.cis.cis-6-1-4
diff --git a/tests/example/file_template.jinja b/tests/example/file_template.jinja
new file mode 100644
index 0000000..1779fcd
--- /dev/null
+++ b/tests/example/file_template.jinja
@@ -0,0 +1 @@
+foo{{ pillar["test"]["example"] }}
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index 0b792b6..de1da29 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -99,6 +99,7 @@
             subjects:
             - '@group1'
     sysfs:
+      enable_apply: true
       scheduler:
         block/sda/queue/scheduler: deadline
       power:
@@ -127,6 +128,7 @@
         enabled: true
         home: /root
         name: root
+        maxdays: 365
       testuser:
         enabled: true
         name: testuser
@@ -135,6 +137,7 @@
         uid: 9999
         full_name: Test User
         home: /home/test
+        unique: false
         groups:
           - db-ops
           - salt-ops
@@ -201,6 +204,22 @@
         proxy:
           enabled: true
           https: https://127.0.5.1:443
+      saltstack:
+        source: "deb [arch=amd64] http://repo.saltstack.com/apt/ubuntu/16.04/amd64/2017.7/ xenial main"
+        key_url: "http://repo.saltstack.com/apt/ubuntu/16.04/amd64/2017.7/SALTSTACK-GPG-KEY.pub"
+        architectures: amd64
+        clean_file: true
+        pinning:
+          10:
+            enabled: true
+            pin: 'release o=SaltStack'
+            priority: 50
+            package: 'libsodium18'
+          20:
+            enabled: true
+            pin: 'release o=SaltStack'
+            priority: 1100
+            package: '*'
       opencontrail:
         source: "deb http://ppa.launchpad.net/tcpcloud/contrail-3.0/ubuntu xenial main"
         keyid: E79EE90C
@@ -407,6 +426,12 @@
         - .local
       LANG: C
       LC_ALL: C
+    login_defs:
+      PASS_MAX_DAYS:
+        value: 99
+    shell:
+      umask: '027'
+      timeout: 900
     profile:
       vi_flavors.sh: |
         export PAGER=view
diff --git a/tests/pillar/system_duo.sls b/tests/pillar/system_duo.sls
new file mode 100644
index 0000000..114324e
--- /dev/null
+++ b/tests/pillar/system_duo.sls
@@ -0,0 +1,212 @@
+linux:
+  network:
+    enabled: false
+    hostname: linux
+    fqdn: linux.ci.local
+  system:
+    enabled: true
+    at:
+      enabled: false
+      user:
+        root:
+          enabled: true
+        testuser:
+          enabled: true
+    cron:
+      enabled: false
+      user:
+        root:
+          enabled: false
+    cluster: default
+    name: linux
+    domain: ci.local
+    environment: prd
+    purge_repos: true
+    directory:
+      /tmp/test:
+        makedirs: true
+    apparmor:
+      enabled: false
+    haveged:
+      enabled: true
+    prompt:
+      default: "linux.ci.local$"
+    package:
+      htop:
+        version: latest
+    repo:
+      disabled_repo:
+        source: "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
+        enabled: false
+      disabled_repo_left_proxy:
+        source: "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
+        enabled: false
+        proxy:
+          enabled: true
+          https: https://127.0.5.1:443
+      saltstack:
+        source: "deb [arch=amd64] http://repo.saltstack.com/apt/ubuntu/16.04/amd64/2017.7/ xenial main"
+        key_url: "http://repo.saltstack.com/apt/ubuntu/16.04/amd64/2017.7/SALTSTACK-GPG-KEY.pub"
+        architectures: amd64
+        clean_file: true
+        pinning:
+          10:
+            enabled: true
+            pin: 'release o=SaltStack'
+            priority: 50
+            package: 'libsodium18'
+          20:
+            enabled: true
+            pin: 'release o=SaltStack'
+            priority: 1100
+            package: '*'
+      apt-salt:
+        source: "deb http://apt.mirantis.com/xenial stable salt"
+        #key_url: http://apt.mirantis.com/public.gpg
+        # pub   4096R/A76882D3 2015-06-17
+        key: |
+          -----BEGIN PGP PUBLIC KEY BLOCK-----
+          Version: GnuPG v1
+
+          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
+          jtcwJrHue5Xn8UPSLkE=
+          =SWiA
+          -----END PGP PUBLIC KEY BLOCK-----
+        architectures: amd64
+        proxy:
+          enabled: true
+      apt-salt-nightly:
+        source: "deb http://apt.mirantis.com/xenial nightly salt"
+        key_url: http://apt.mirantis.com/public.gpg
+        architectures: amd64
+        proxy:
+          enabled: false
+      apt-extra-nightly:
+        source: "deb http://apt.mirantis.com/xenial nightly extra"
+        key_url: http://apt.mirantis.com/public.gpg
+        architectures: amd64
+      duo:
+        key: |
+          -----BEGIN PGP PUBLIC KEY BLOCK-----
+          Version: GnuPG v2.0.22 (GNU/Linux)
+
+          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
+          WbTIiN63dr6gdz7hoZJ7PFmJ
+          =t1j7
+          -----END PGP PUBLIC KEY BLOCK-----
+        source: "deb [arch=amd64] http://pkg.duosecurity.com/Ubuntu xenial main"
+        architectures: amd64
+    locale:
+      en_US:
+        enabled: true
+        default: true
+      cs_CZ:
+        enabled: true
+    autoupdates:
+      enabled: true
+    sudo:
+      enabled: false
+    env:
+      BOB_VARIABLE: Alice
+      LANG: C
+      LC_ALL: C
+    login_defs:
+      PASS_MAX_DAYS:
+        value: 99
+    shell:
+      umask: '027'
+      timeout: 900
+    profile:
+      vi_flavors.sh: |
+        export PAGER=view
+        alias vi=vim
+      locales: |
+        export LANG=en_US
+        export LC_ALL=en_US.UTF-8
+    auth:
+      enabled: true
+      duo:
+        enabled: true
+        duo_host: localhost
+        duo_ikey: DUO-INTEGRATION-KEY
+        duo_skey: DUO-SECRET-KEY
+
diff --git a/tests/pillar/system_file.sls b/tests/pillar/system_file.sls
index 0769b18..09900af 100644
--- a/tests/pillar/system_file.sls
+++ b/tests/pillar/system_file.sls
@@ -23,4 +23,10 @@
         mode: 700
         dir_mode: 700
         encoding: utf-8
-        makedirs: true
\ No newline at end of file
+        makedirs: true
+      test3:
+        name: /tmp/test3.txt
+        source: salt://linux/files/test/file_template.jinja
+        template: jinja
+test:
+  example: "bar"