mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Porting PR #51840 to 2019.2.1
This commit is contained in:
parent
9822bc592f
commit
627f0c4a7c
5 changed files with 203 additions and 0 deletions
|
@ -20,3 +20,4 @@ roster modules
|
|||
scan
|
||||
sshconfig
|
||||
terraform
|
||||
sshknownhosts
|
||||
|
|
6
doc/ref/roster/all/salt.roster.sshknownhosts.rst
Normal file
6
doc/ref/roster/all/salt.roster.sshknownhosts.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
=========================
|
||||
salt.roster.sshknownhosts
|
||||
=========================
|
||||
|
||||
.. automodule:: salt.roster.sshknownhosts
|
||||
:members:
|
107
salt/roster/sshknownhosts.py
Normal file
107
salt/roster/sshknownhosts.py
Normal 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')
|
5
tests/unit/files/rosters/sshknownhosts/known_hosts
Normal file
5
tests/unit/files/rosters/sshknownhosts/known_hosts
Normal 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=
|
84
tests/unit/roster/test_sshknownhosts.py
Normal file
84
tests/unit/roster/test_sshknownhosts.py
Normal 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)
|
Loading…
Add table
Reference in a new issue