Porting PR #51840 to 2019.2.1

This commit is contained in:
Gareth J. Greenaway 2019-09-18 18:17:58 -07:00 committed by Gareth J. Greenaway
parent 9822bc592f
commit 627f0c4a7c
5 changed files with 203 additions and 0 deletions

View file

@ -20,3 +20,4 @@ roster modules
scan
sshconfig
terraform
sshknownhosts

View file

@ -0,0 +1,6 @@
=========================
salt.roster.sshknownhosts
=========================
.. automodule:: salt.roster.sshknownhosts
:members:

View file

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
'''
Parses roster entries out of Host directives from SSH known_hosts
Sample configuration:
.. note::
The ``known_hosts`` file only contains hostname/IP. To pass other parameters,
use ``roster_defaults``.
.. code-block:: yaml
ssh_known_hosts_file: /Users/user1/.ssh/known_hosts
roster_defaults:
user: user1
sudo: True
Now you can use the module
.. code-block:: bash
salt-ssh --roster sshknownhosts '*' -r "echo hi"
Or with a Saltfile
.. code-block:: yaml
salt-ssh:
ssh_known_hosts_file: /Users/user1/.ssh/known_hosts
.. code-block:: bash
salt-ssh --roster sshknownhosts '*' -r "echo hi"
'''
from __future__ import absolute_import, print_function, unicode_literals
# Import python libs
import os
import logging
# Import Salt libs
import salt.utils.files
import salt.utils.stringutils
log = logging.getLogger(__name__)
def _parse_ssh_known_hosts_line(line):
'''
Parse one line from a known_hosts line
:param line: Individual lines from the ssh known_hosts file
:return: Dict that contain the three fields from a known_hosts line
'''
line_unicode = salt.utils.stringutils.to_unicode(line)
fields = line_unicode.split(" ")
if len(fields) < 3:
log.warn("Not enough fields found in known_hosts in line : %s", line)
return None
fields = fields[:3]
names, keytype, key = fields
names = names.split(",")
return {'names': names, 'keytype': keytype, 'key': key}
def _parse_ssh_known_hosts(lines):
'''
Parses lines from the SSH known_hosts to create roster targets.
:param lines: lines from the ssh known_hosts file
:return: Dictionary of targets in similar style to the flat roster
'''
targets_ = {}
for line in lines:
host_key = _parse_ssh_known_hosts_line(line)
for host in host_key['names']:
targets_.update({host: {'host': host}})
return targets_
def targets(tgt, tgt_type='glob'):
'''
Return the targets from a known_hosts file
'''
ssh_known_hosts_file = __opts__.get('ssh_known_hosts_file')
if not os.path.isfile(ssh_known_hosts_file):
log.error('Cannot find SSH known_hosts file')
raise IOError('Cannot find SSH known_hosts file')
if not os.access(ssh_known_hosts_file, os.R_OK):
log.error('Cannot access SSH known_hosts file: %s', ssh_known_hosts_file)
raise IOError('Cannot access SSH known_hosts file: {}'.format(ssh_known_hosts_file))
with salt.utils.files.fopen(ssh_known_hosts_file, 'r') as hostfile:
raw = _parse_ssh_known_hosts([line.rstrip() for line in hostfile])
return __utils__['roster_matcher.targets'](raw, tgt, tgt_type, 'ipv4')

View file

@ -0,0 +1,5 @@
server1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD0vTE0R76xiKEXAdebZW0a3xGLeP2Fet/5YHQgprry3wuXzjBJwGcm8PVFNfbK/C7oAgFUg8NVX7xqQnScekJg=
server2 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAui+dKujjKF92dDdM9hZzCd+BdTDRnvsWqMf88NjushOmFCt/8zXbB1TvYQmdCcy1qXqhmkbgUdtVuLHnhncf/niCtyih3K3ZR7NpecBydcC+0xv0UeXk/xCGcwM2V0BuukrV/5qRqhyG0rK1hd+Iv9fkB0/s8D/HLcEB1/V4g77XxPGnI7lNANFbZpWs1LrnAec7JIkHO9MHEfuhQWZR6+/iIXIwQoc1RCToQbWQFCYFwrnDrAUHC2+izJiP2VDNW6xboVcf6DpwydfYvFdM8Mo97DEcchlwIWhmGl//LpnwafujFZCE5vDveA8X4uKZEXxoCmUPIGfkx6xIzzTkqQ==
server3.local ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAgKWpCT7JIeK/qzwE5lUQkLRfkRa5WnyyeF+aYCKDUHB4b4Pn+acm8FOca+riulPDY/gJhb0MX3Rf/t6MrEHQA=
eu-mysql-1.local ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAsuToIp6iqJ3lHPQzCTiNf5F8uf/CjAljuxRjURYCbQydts2lnnqTpjamL1b8/FpvB1dDlA71G79yTftVZ8EqL2VaN0tL242MXaqy2nmeVjy89dtOyk35IHwQe8Bi6mu3vLYCFnysiAvrtLQMFe8jNjndsvf27LNKox8pIAyOyN3hONL+bXEcPB2RjIUL8wS8uTeOueuPbVwc1cHkUuMjlNzsH3l6KMVjJZ8keFdRj8iogV8oZGR3KGoPfX4aZDt9S+L/k97fWkOhSKLWkKbplEcmIjuF5pgZLO3Wf35eLZN12PcHuX7WFWZi+UxjJDW2VLaP867La4YXDEU3LNdPEQ==
eu-mysql-2,eu-mysql-2.local ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHFnjWT+gnUGRA2zW+LGZdebSkUVKBb6F/XCcDrtBZmaxCNaS/+F6SYzXP4MghCQhXFEPd7MpFnwPV8giU1NUag=

View file

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
# Import Salt Testing Libs
from tests.support.mock import (
NO_MOCK,
NO_MOCK_REASON,
patch
)
from tests.support import mixins
from tests.support.unit import skipIf, TestCase
from tests.support.runtests import RUNTIME_VARS
# Import Salt Libs
import salt.config
import salt.loader
import salt.roster.sshknownhosts as sshknownhosts
_ALL = {
'server1': {'host': 'server1'},
'server2': {'host': 'server2'},
'server3.local': {'host': 'server3.local'},
'eu-mysql-1.local': {'host': 'eu-mysql-1.local'},
'eu-mysql-2': {'host': 'eu-mysql-2'},
'eu-mysql-2.local': {'host': 'eu-mysql-2.local'}
}
_TEST_GLOB = {
'server1': {'host': 'server1'},
'server2': {'host': 'server2'},
'server3.local': {'host': 'server3.local'}
}
_TEST_PCRE = {
'eu-mysql-2': {'host': 'eu-mysql-2'}
}
@skipIf(NO_MOCK, NO_MOCK_REASON)
class SSHKnownHostsRosterTestCase(TestCase, mixins.LoaderModuleMockMixin):
@classmethod
def setUpClass(cls):
cls.tests_dir = os.path.join(RUNTIME_VARS.TESTS_DIR, 'unit/files/rosters/sshknownhosts/')
cls.opts = {'ssh_known_hosts_file': 'known_hosts'}
@classmethod
def tearDownClass(cls):
delattr(cls, 'tests_dir')
delattr(cls, 'opts')
def setup_loader_modules(self):
opts = salt.config.master_config(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'master'))
utils = salt.loader.utils(opts)
runner = salt.loader.runner(opts, utils=utils)
return {
sshknownhosts: {
'__utils__': utils,
'__opts__': {},
'__runner__': runner
}
}
def test_all(self):
self.opts['ssh_known_hosts_file'] = os.path.join(self.tests_dir, 'known_hosts')
with patch.dict(sshknownhosts.__opts__, self.opts):
targets = sshknownhosts.targets(tgt='*')
self.assertDictEqual(targets, _ALL)
def test_glob(self):
self.opts['ssh_known_hosts_file'] = os.path.join(self.tests_dir, 'known_hosts')
with patch.dict(sshknownhosts.__opts__, self.opts):
targets = sshknownhosts.targets(tgt='server*')
self.assertDictEqual(targets, _TEST_GLOB)
def test_pcre(self):
self.opts['ssh_known_hosts_file'] = os.path.join(self.tests_dir, 'known_hosts')
with patch.dict(sshknownhosts.__opts__, self.opts):
targets = sshknownhosts.targets(tgt='eu-mysql-2$', tgt_type='pcre')
self.assertDictEqual(targets, _TEST_PCRE)