Skip to content
This repository was archived by the owner on Dec 21, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
language: python
python:
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "3.6-dev"
- "3.7-dev"
- "3.7"
- "3.8"
- "nightly"
install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
Expand Down
6 changes: 6 additions & 0 deletions constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
setuptools>18.5,<46;python_version<'3.5'
zipp<2;python_version<'3.6'
coverage==3.7.1;python_version<'3.8'
coverage>=5.0.0;python_version>='3.8'
tox<=3.14.0;python_version<'3.4'
tox>3.14.0;python_version>='3.4'
6 changes: 3 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nose==1.3.1
coverage==3.7.1
tox==1.7.1
nose
coverage
tox
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'License :: OSI Approved :: MIT License',
'Topic :: System :: Systems Administration',
]
Expand Down
6 changes: 3 additions & 3 deletions storm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ERRORS = {
"already_in": "{0} is already in your sshconfig. "
"use storm edit or storm update to modify.",
"not_found": "{0} doesn\'t exists in your sshconfig. "
"not_found": "{0} doesn't exist in your sshconfig. "
"use storm add command to add.",
}

Expand Down Expand Up @@ -45,11 +45,11 @@ def clone_entry(self, name, clone_name, keep_original=True):
if not host:
raise ValueError(ERRORS["not_found"].format(name))

# check if an entry with the clone name already exists
# check if an entry with the clone name already exists
if name == clone_name \
or self.is_host_in(clone_name, return_match=True) is not None:
raise ValueError(ERRORS["already_in"].format(clone_name))

self.ssh_config.add_host(clone_name, host.get('options'))
if not keep_original:
self.ssh_config.delete_host(name)
Expand Down
7 changes: 5 additions & 2 deletions storm/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

from storm import Storm
from storm.parsers.ssh_uri_parser import parse
from storm.utils import (get_formatted_message, colored)
from storm.parsers.ssh_config_parser import lower_config_map
from storm.utils import get_formatted_message, colored
from storm.kommandr import *
from storm.defaults import get_default
from storm import __version__
Expand Down Expand Up @@ -227,7 +228,8 @@ def list(config=None):
if isinstance(value, collections.Sequence):
if isinstance(value, builtins.list):
value = ",".join(value)


key = lower_config_map.get(key.lower(), key)
result += "{0}={1} ".format(key, value)
if extra:
result = result[0:-1]
Expand All @@ -238,6 +240,7 @@ def list(config=None):
" (*) General options: \n", "green", attrs=["bold",]
)
for key, value in six.iteritems(host.get("options")):
key = lower_config_map.get(key.lower(), key)
if isinstance(value, type([])):
result_stack += "\t {0}: ".format(
colored(key, "magenta")
Expand Down
99 changes: 99 additions & 0 deletions storm/parsers/ssh_config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,104 @@ def parse(self, file_obj):
self._config.append(host)


# Current as of OpenSSH 8.2
# Source: https://man.openbsd.org/ssh_config
config_options = [
"Host",
"Match",
"AddKeysToAgent",
"AddressFamily",
"BatchMode",
"BindAddress",
"BindInterface",
"CanonicalDomains",
"CanonicalizeFallbackLocal",
"CanonicalizeHostname",
"CanonicalizeMaxDots",
"CanonicalizePermittedCNAMEs",
"CASignatureAlgorithms",
"CertificateFile",
"ChallengeResponseAuthentication",
"CheckHostIP",
"Ciphers",
"ClearAllForwardings",
"Compression",
"ConnectionAttempts",
"ConnectTimeout",
"ControlMaster",
"ControlPath",
"ControlPersist",
"DynamicForward",
"EnableSSHKeysign",
"EscapeChar",
"ExitOnForwardFailure",
"FingerprintHash",
"ForwardAgent",
"ForwardX11",
"ForwardX11Timeout",
"ForwardX11Trusted",
"GatewayPorts",
"GlobalKnownHostsFile",
"GSSAPIAuthentication",
"GSSAPIDelegateCredentials",
"HashKnownHosts",
"HostbasedAuthentication",
"HostbasedKeyTypes",
"HostKeyAlgorithms",
"HostKeyAlias",
"Hostname",
"IdentitiesOnly",
"IdentityAgent",
"IdentityFile",
"IgnoreUnknown",
"Include",
"IPQoS",
"KbdInteractiveAuthentication",
"KbdInteractiveDevices",
"KexAlgorithms",
"LocalCommand",
"LocalForward",
"LogLevel",
"MACs",
"NoHostAuthenticationForLocalhost",
"NumberOfPasswordPrompts",
"PasswordAuthentication",
"PermitLocalCommand",
"Port",
"PreferredAuthentications",
"ProxyCommand",
"ProxyJump",
"ProxyUseFdpass",
"PubkeyAcceptedKeyTypes",
"PubkeyAuthentication",
"RekeyLimit",
"RemoteCommand",
"RemoteForward",
"RequestTTY",
"RevokedHostKeys",
"SecurityKeyProvider",
"SendEnv",
"ServerAliveCountMax",
"ServerAliveInterval",
"SetEnv",
"StreamLocalBindMask",
"StreamLocalBindUnlink",
"StrictHostKeyChecking",
"SyslogFacility",
"TCPKeepAlive",
"Tunnel",
"TunnelDevice",
"UpdateHostKeys",
"User",
"UserKnownHostsFile",
"VerifyHostKeyDNS",
"VisualHostKey",
"XAuthLocation",
]

lower_config_map = {x.lower(): x for x in config_options}


class ConfigParser(object):
"""
Config parser for ~/.ssh/config files.
Expand Down Expand Up @@ -214,6 +312,7 @@ def dump(self):
continue
host_item_content = "Host {0}\n".format(host_item.get("host"))
for key, value in six.iteritems(host_item.get("options")):
key = lower_config_map.get(key.lower(), key)
if isinstance(value, list):
sub_content = ""
for value_ in value:
Expand Down
46 changes: 23 additions & 23 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,24 @@ def test_list_command(self):
"server1 -> [email protected]:4242",
"uk.gw.lan uk.lan -> [email protected]:22",
], [
"[custom options] identityfile=~/.ssh/aws.apache.key",
"[custom options] identityfile=~/.ssh/nas01.key",
"identityfile=~/.ssh/vps1.cyberciti.biz.key",
"localforward=3128 127.0.0.1:3128",
"[custom options] identityfile=/nfs/shared/users/nixcraft/keys/server1/id_rsa,/tmp/x.rsa",
"[custom options] proxycommand=ssh [email protected] nc %h %p 2> /dev/null",
"[custom options] IdentityFile=~/.ssh/aws.apache.key",
"[custom options] IdentityFile=~/.ssh/nas01.key",
"IdentityFile=~/.ssh/vps1.cyberciti.biz.key",
"LocalForward=3128 127.0.0.1:3128",
"[custom options] IdentityFile=/nfs/shared/users/nixcraft/keys/server1/id_rsa,/tmp/x.rsa",
"[custom options] ProxyCommand=ssh [email protected] nc %h %p 2> /dev/null",
]

general_options = {
"forwardx11": "no",
"ForwardX11": "no",
"protocol": "2",
"user": "nixcraft",
"forwardagent": "no",
"forwardx11trusted": "yes",
"serveralivecountmax": "30",
"serveraliveinterval": "60",
"port": "22",
"localforward": "3128 127.0.0.1:3128, 3129 127.0.0.1:3128",
"User": "nixcraft",
"ForwardAgent": "no",
"ForwardX11Trusted": "yes",
"ServerAliveCountMax": "30",
"ServerAliveInterval": "60",
"Port": "22",
"LocalForward": "3128 127.0.0.1:3128, 3129 127.0.0.1:3128",
}

for host in hosts:
Expand Down Expand Up @@ -175,9 +175,9 @@ def test_advanced_add(self):
with open(self.config_file) as f:
# check that property is really flushed out to the config?
content = f.read().encode('ascii')
self.assertIn(b'identityfile "/tmp/idfilecheck.rsa"', content)
self.assertIn(b"stricthostkeychecking yes", content)
self.assertIn(b"userknownhostsfile /dev/advanced_test", content)
self.assertIn(b'IdentityFile "/tmp/idfilecheck.rsa"', content)
self.assertIn(b"StrictHostKeyChecking yes", content)
self.assertIn(b"UserKnownHostsFile /dev/advanced_test", content)

def test_add_with_idfile(self):
out, err, rc = self.run_cmd('add postgresql-server [email protected] {0} {1}'.format(
Expand All @@ -190,7 +190,7 @@ def test_add_with_idfile(self):

with open(self.config_file) as f:
content = f.read().encode('ascii')
self.assertIn(b'identityfile "/tmp/idfileonlycheck.rsa"', content)
self.assertIn(b'IdentityFile "/tmp/idfileonlycheck.rsa"', content)

def test_basic_edit(self):
out, err, rc = self.run_cmd('edit aws.apache [email protected] {0}'.format(self.config_arg))
Expand Down Expand Up @@ -225,8 +225,8 @@ def test_update(self):

with open(self.config_file) as f:
content = f.read().encode('ascii')
self.assertIn(b"user daghan", content) # see daghan: http://instagram.com/p/lfPMW_qVja
self.assertIn(b"port 42000", content)
self.assertIn(b"User daghan", content) # see daghan: http://instagram.com/p/lfPMW_qVja
self.assertIn(b"Port 42000", content)

def test_update_regex(self):

Expand Down Expand Up @@ -336,7 +336,7 @@ def test_config_load(self):
def test_config_dump(self):
self.storm.ssh_config.write_to_ssh_config()

for search_str in ("hostname 1.1.1.1", "Host netscaler", "Host *"):
for search_str in ("Hostname 1.1.1.1", "Host netscaler", "Host *"):
with open('/tmp/ssh_config') as fd:
self.assertIn(search_str, fd.read())

Expand All @@ -360,11 +360,11 @@ def test_clone_host(self):

has_yahoo = False
for item in self.storm.ssh_config.config_data:
if item.get("host") == 'yahoo':
if item.get("host") == 'yahoo':
has_yahoo = True
break

self.assertEqual(True, has_yahoo)
self.assertEqual(True, has_yahoo)
self.assertEqual(item.get("options").get("port"), '24')
self.assertEqual(item.get("options").get("identityfile"), '"/tmp/tmp.pub"')
self.assertEqual(item.get("options").get("user"), 'ops')
Expand Down
13 changes: 6 additions & 7 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
[tox]
envlist = py27,py32,py33,py34
envlist = py27,py32,py33,py34,py35,py36,py37,py38
skipsdist = True

[testenv]
whitelist_externals = rm
commands_pre =
rm -f .coverage
commands =
pip install --allow-all-external -e .
pip install -e .
nosetests
deps =
-cconstraints.txt
-rrequirements-dev.txt

[testenv:py26]
deps =
{[testenv]deps}
unittest2