Support patterns in sshd_config

AllowUsers, DenyUsers, AllowGroups, DenyGroups should support
comma-separated list of patterns (man sshd_config, man ssh_config)

Change-Id: I889ae8027d036a174c5fec2713b0b0e3f9a3e544
diff --git a/.gitignore b/.gitignore
index aa8e42a..a1257c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,7 @@
 .kitchen
+.bundle/
+*Gemfile/
+bundle/
 tests/build/
 *.swp
 *.pyc
diff --git a/README.rst b/README.rst
index d534a2e..8254184 100644
--- a/README.rst
+++ b/README.rst
@@ -154,6 +154,32 @@
       server:
         dss_enabled: true
 
+* OpenSSH server configuration supports AllowUsers, DenyUsers, AllowGroup,
+DenyGroups via allow_users, deny_users, allow_groups, deny_groups keys respectively.
+
+For example, here is how to manage AllowUsers configuration item:
+
+  .. code-block:: yaml
+
+    openssh:
+      server:
+        allow_users:
+          <user_name>:
+            enabled: true
+          <pattern_list_name>:
+            enabled: true
+            pattern: <pattern>
+
+Elements of allow_users are either user names or pattern list names:
+* <user name> goes to configurational file as is.
+* <pattern list name> is not used directly - its main purpose is to provide a
+  meaningfull name for a pattern specified in 'pattern' key. Another advantage
+  is that pattern can be overriden.
+
+<enabled> by default is 'true'.
+
+See PATTERNS in ssh_config(5) for more information on what <pattern> is.
+
 **CIS Compliance**
 
 There is a number of configuration options that make the OpenSSH service
diff --git a/openssh/files/sshd_config b/openssh/files/sshd_config
index 5404e84..7fff6e2 100755
--- a/openssh/files/sshd_config
+++ b/openssh/files/sshd_config
@@ -66,17 +66,31 @@
 ForceCommand {{ server.force_command }}
 {%- endif %}
 
+{%- macro patterns(pillar_key) %}
+  {%- set pillar_data = server.get(pillar_key, none) %}
+  {%- if pillar_data is mapping %}
+    {%- set patterns = [] %}
+    {%- for name, params in pillar_data.items() %}
+      {%- if params.get('enabled', true) %}
+        {%- do patterns.append(params.get('pattern', name)) %}
+      {%- endif %}
+    {%- endfor %}
+{{- patterns | join(' ') }}
+  {%- elif pillar_data is iterable %}
+{{- pillar_data | join(' ') }}
+  {%- endif %}
+{%- endmacro %}
 {% if server.get('deny_users', False) %}
-DenyUsers {{ server.deny_users|join(' ') }}
+DenyUsers {{ patterns('deny_users') }}
 {% endif %}
 {% if server.get('allow_users', False) %}
-AllowUsers {{ server.allow_users|join(' ') }}
+AllowUsers {{ patterns('allow_users') }}
 {% endif %}
 {% if server.get('deny_groups', False) %}
-DenyGroups {{ server.deny_groups|join(' ') }}
+DenyGroups {{ patterns('deny_groups') }}
 {% endif %}
 {% if server.get('allow_groups', False) %}
-AllowGroups {{ server.allow_groups|join(' ') }}
+AllowGroups {{ patterns('allow_groups') }}
 {% endif %}
 
 IgnoreRhosts {{ 'yes' if server.get('ignore_rhosts', True) else 'no' }}
diff --git a/openssh/schemas/server.yaml b/openssh/schemas/server.yaml
index cceb110..23b28b5 100644
--- a/openssh/schemas/server.yaml
+++ b/openssh/schemas/server.yaml
@@ -16,6 +16,42 @@
     description: |
       Enables openssh server configurathion.
     type: boolean
+  allow_users:
+    type: object
+    description: |
+      This keyword can be followed by a list of user name patterns,
+      separated by spaces. If specified, login is allowed only for user names
+      that match one of the patterns.
+    properties:
+      enabled:
+        type: boolean
+  deny_users:
+    type: object
+    description: |
+      This keyword can be followed by a list of user name patterns,
+      separated by spaces. Login is disallowed for user names that match one
+      of the patterns.
+    properties:
+      enabled:
+        type: boolean
+  allow_groups:
+    type: object
+    description: |
+      This keyword can be followed by a list of group name patterns, separated
+      by spaces. If specified, login is allowed only for users whose primary
+      group or supplementary group list matches one of the patterns.
+    properties:
+      enabled:
+        type: boolean
+  deny_groups:
+    type: object
+    description: |
+      This keyword can be followed by a list of group name patterns, separated
+      by spaces. Login is disallowed for users whose primary group or
+      supplementary group list matches one of the patterns.
+    properties:
+      enabled:
+        type: boolean
   banner:
     description: |
       Banner
diff --git a/tests/pillar/server.sls b/tests/pillar/server.sls
index 4bdf62b..bc2b3f0 100644
--- a/tests/pillar/server.sls
+++ b/tests/pillar/server.sls
@@ -32,3 +32,23 @@
       keep: no
       interval: 600
       # count: 3
+    allow_users:
+      allowed_user:
+        enabled: true
+      test_pattern:
+        pattern: "!*@*.dialup.example.com,*@*.example.com"
+    deny_users:
+      denied_user:
+        enabled: true
+      test_pattern:
+        pattern: "*@*.dialup.example.com"
+    allow_groups:
+      admin:
+        enabled: true
+      operator:
+        enabled: true
+    deny_groups:
+      root:
+        enabled: true
+      wheel:
+        enabled: true