Add all hosts to known_hosts automatically

	- optimize known_hosts.present state
	- add ability to define port
	- remove undefined known_hosts if enabled

Related: PROD-25697

Change-Id: Idbaeac91d271693905b53eccf8f6249e7aa274e7
(cherry picked from commit 186b01fb589c04baa2b7eedf0532d1824dbab8f8)
diff --git a/README.rst b/README.rst
index c2d3a3b..35a80f2 100644
--- a/README.rst
+++ b/README.rst
@@ -48,6 +48,31 @@
                 fingerprint: dd:fa:e8:68:b1:ea:ea:a0:63:f1:5a:55:48:e1:7e:37
                 fingerprint_hash_type: sha256|md5
 
+* The OpenSSH client configuration with definition of known_hosts using
+public key instead of fingerprint
+
+  - If `purge_defined` is set to `true` it will remove old known_hosts file
+    and start with a fresh one causing that known_host undefined in the pillar
+    will not be included.
+
+  .. code-block:: yaml
+
+      openssh:
+        client:
+          enabled: true
+          user:
+            root:
+              enabled: true
+              purge_undefined: false
+              user:
+                name: 'root'
+                home: '/root'
+              known_hosts:
+              - name: 10.11.1.50
+                port: 22
+                type: ssh-rsa
+                host_public_key: AAA...fkP
+
 * The OpenSSH client configuration with keep alive settings:
 
   .. code-block:: yaml
diff --git a/openssh/client/known_host.sls b/openssh/client/known_host.sls
index 8cf4a95..664e138 100644
--- a/openssh/client/known_host.sls
+++ b/openssh/client/known_host.sls
@@ -6,17 +6,44 @@
 
 {%- for xxx, user in client.get('user', {}).iteritems() %}
 
+{%- if user.get('purge_undefined', False) %}
+{{ user.user.name }}_remove_old_known_hosts_file:
+  cmd.run:
+  - name: "if [ -e {{ user.user.home }}/.ssh/known_hosts ]; then mv {{ user.user.home }}/.ssh/known_hosts {{ user.user.home }}/.ssh/known_hosts_$(date +%F_%H-%M-%S); fi"
+  - require:
+    - file: {{ user.user.home }}/.ssh
+
+{{ user.user.name }}_create_empty_known_hosts_file:
+  file.touch:
+  - name: {{ user.user.home }}/.ssh/known_hosts
+{%- endif %}
+
 {%- for host in user.get('known_hosts', []) %}
 
-{{ user.user.name }}_known_hosts_{{ host.name }}:
+{%- if host.port is defined %}
+{{ user.user.name }}_known_hosts_{{ host.name }}_{{ host.port }}-{{ loop.index }}:
+{%- else %}
+{{ user.user.name }}_known_hosts_{{ host.name }}-{{ loop.index }}:
+{%- endif %}
   ssh_known_hosts.present:
   - user: {{ user.user.name }}
   - name: {{ host.name }}
   - enc: {{ host.get('type', 'ecdsa') }}
-  {%- if host.fingerprint_hash_type is defined %}
-  - fingerprint_hash_type: {{ host.fingerprint_hash_type }}
-  {%- endif %}
+  {%- if host.fingerprint is defined %}
   - fingerprint: {{ host.fingerprint }}
+    {%- if host.fingerprint_hash_type is defined %}
+  - fingerprint_hash_type: {{ host.fingerprint_hash_type }}
+    {%- endif %}
+  {%- endif %}
+  {%- if host.host_public_key is defined %}
+  - key: {{ host.host_public_key }}
+  {%- endif %}
+  {%- if host.port is defined %}
+  - port: {{ host.port }}
+  {%- endif %}
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
   - require:
     - pkg: openssh_client_packages
     - file: {{ user.user.home }}/.ssh
diff --git a/openssh/map.jinja b/openssh/map.jinja
index 5cbda1d..0a0fc88 100644
--- a/openssh/map.jinja
+++ b/openssh/map.jinja
@@ -1,3 +1,26 @@
+{% if pillar.openssh.get('client', {}).get('enabled', False) %}
+{% set local_hosts = salt['pillar.get']('linux:network:host') %}  {# Get all defined hosts #}
+{% set root_public_key = salt['pillar.get']('_param:root_public_key')|replace('ssh-rsa', '') %}  {# Get root's public key #}
+{% set root = salt['pillar.get'](key='openssh:client:user:root', default={}) %} {# Get predefined root user pillar data if there are any #}
+{% if root.known_hosts is not defined %}
+  {% do root.update({'known_hosts': []}) %} {# Prepare empty list if missing so it can be appended later #}
+{% endif %}
+{% for host_name, host in local_hosts.iteritems() %}  {# Iterate through all defined hosts #}
+  {% if host.address is defined %}
+    {% set new_host = {'name': host.address, 'host_public_key': root_public_key, 'type': 'ssh-rsa'} %}
+    {% do root['known_hosts'].append(new_host) %} {# Add the host to the list of known hosts #}
+  {% endif %}
+  {% for hostname in host.get('names', []) %}  {# Do the same again, but this time with all hostnames instead of IP addresses #}
+    {% set new_host = {'name': hostname, 'host_public_key': root_public_key, 'type': 'ssh-rsa'} %}
+    {% do root['known_hosts'].append(new_host) %}
+  {% endfor %}
+{% endfor %}
+{% if root['user'] is not defined %}
+  {% do root.update({'user': {}}) %}
+  {% do root['user'].update({'name': 'root', 'home': '/root'}) %}  {# If remaining required data is not configured yet, do it now. #}
+{% endif %}
+{% endif %}
+
 {% set server = salt['grains.filter_by']({
     'Arch': {
         'pkgs': ['openssh'],
@@ -47,6 +70,9 @@
         'pkgs': ['openssh-client'],
         'proxy_pkgs': ['connect-proxy'],
         'config': '/etc/ssh/ssh_config',
+        'user': {
+            'root': root,
+        }
     },
     'MacOS': {
         'pkgs': ['openssh'],
diff --git a/openssh/schemas/client.yaml b/openssh/schemas/client.yaml
index 3000b68..55655df 100644
--- a/openssh/schemas/client.yaml
+++ b/openssh/schemas/client.yaml
@@ -81,6 +81,9 @@
         description: Define user private_key type
         type: string
         example: "rsa"
+      purge_undefined:
+        description: Defines if the known_hosts file should be removed before adding defined keys
+        type: boolean
       user:
         description: Define user login data. In scope of openssh - uses only to acquire user homepath
         additionalProperties: false
@@ -103,6 +106,12 @@
       fingerprint:
         type: string
         example: dd:fa:e8:68:b1:ea:ea:a0:63:f1:5a:55:48:e1:7e:37
+      port:
+        type: integer
+        example: 22
+      host_public_key:
+        type: string
+        example: AAAAB3NzaXXXXAAAXXXX
 
   _global_useradd_user:
     description: Define exactly one linux user login data.
diff --git a/tests/pillar/client.sls b/tests/pillar/client.sls
index bf746d9..b124cc3 100644
--- a/tests/pillar/client.sls
+++ b/tests/pillar/client.sls
@@ -9,6 +9,32 @@
         uid: 9999
         full_name: Test User
         home: /home/testusername
+  network:
+    host:
+      cfg:
+        address:
+            10.11.0.15
+        names:
+            - cfg
+            - cfg.example.com
+      ctl01:
+        address:
+            10.11.0.11
+        names:
+            - ctl01
+            - ctl01.example.com
+      ctl02:
+        address:
+            10.11.0.12
+        names:
+            - ctl02
+            - ctl02.example.com
+      ctl03:
+        address:
+            10.11.0.13
+        names:
+            - ctl03
+            - ctl03.example.com
 openssh:
   client:
     enabled: true
@@ -17,11 +43,12 @@
         enabled: true
         # Include from linux.system.user structure
         user: *_user
-    known_hosts:
-    - name: repo.domain.com
-      type: rsa
-      fingerprint: dd:fa:e8:68:b1:ea:ea:a0:63:f1:5a:55:48:e1:7e:37
-    alive:
-      interval: 600
-      count: 3
-
+        purge_undefined: true
+        known_hosts:
+        - name: localhost
+          type: rsa
+          fingerprint: dd:fa:e8:68:b1:ea:ea:a0:63:f1:5a:55:48:e1:7e:37
+        - name: 127.0.0.1
+          port: 22
+          type: ssh-rsa
+          host_public_key: AAA...fkP