diff --git a/jc/parsers/ssh_conf.py b/jc/parsers/ssh_conf.py index 45ed2cdd..95b2c7ec 100644 --- a/jc/parsers/ssh_conf.py +++ b/jc/parsers/ssh_conf.py @@ -26,6 +26,9 @@ Schema: [ { "host": string, + "host_list": [ + string + ], "addkeystoagent": string, "addressfamily": string, "batchmode": string, @@ -364,6 +367,9 @@ Examples: [ { "host": "server1", + "host_list": [ + "server1" + ], "hostname": "server1.cyberciti.biz", "user": "nixcraft", "port": 4242, @@ -373,6 +379,9 @@ Examples: }, { "host": "nas01", + "host_list": [ + "nas01" + ], "hostname": "192.168.1.100", "user": "root", "identityfile": [ @@ -381,6 +390,9 @@ Examples: }, { "host": "aws.apache", + "host_list": [ + "aws.apache" + ], "hostname": "1.2.3.4", "user": "wwwdata", "identityfile": [ @@ -389,12 +401,19 @@ Examples: }, { "host": "uk.gw.lan uk.lan", + "host_list": [ + "uk.gw.lan", + "uk.lan" + ], "hostname": "192.168.0.251", "user": "nixcraft", "proxycommand": "ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null" }, { "host": "proxyus", + "host_list": [ + "proxyus" + ], "hostname": "vps1.cyberciti.biz", "user": "breakfree", "identityfile": [ @@ -406,6 +425,9 @@ Examples: }, { "host": "*", + "host_list": [ + "*" + ], "forwardagent": "no", "forwardx11": "no", "forwardx11trusted": "yes", @@ -421,6 +443,9 @@ Examples: [ { "host": "server1", + "host_list": [ + "server1" + ], "hostname": "server1.cyberciti.biz", "user": "nixcraft", "port": "4242", @@ -430,6 +455,9 @@ Examples: }, { "host": "nas01", + "host_list": [ + "nas01" + ], "hostname": "192.168.1.100", "user": "root", "identityfile": [ @@ -438,6 +466,9 @@ Examples: }, { "host": "aws.apache", + "host_list": [ + "aws.apache" + ], "hostname": "1.2.3.4", "user": "wwwdata", "identityfile": [ @@ -446,12 +477,19 @@ Examples: }, { "host": "uk.gw.lan uk.lan", + "host_list": [ + "uk.gw.lan", + "uk.lan" + ], "hostname": "192.168.0.251", "user": "nixcraft", "proxycommand": "ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null" }, { "host": "proxyus", + "host_list": [ + "proxyus" + ], "hostname": "vps1.cyberciti.biz", "user": "breakfree", "identityfile": [ @@ -463,6 +501,9 @@ Examples: }, { "host": "*", + "host_list": [ + "*" + ], "forwardagent": "no", "forwardx11": "no", "forwardx11trusted": "yes", @@ -602,7 +643,12 @@ def parse( if line.strip().startswith('Host '): if host: raw_output.append(host) - host = {'host': line.split()[1]} + + hostnames = line.split(maxsplit=1)[1] + host = { + 'host': hostnames, + 'host_list': hostnames.split() + } # support configuration file by ignoring all lines between # Match xxx and Match any diff --git a/tests/fixtures/generic/ssh_config1 b/tests/fixtures/generic/ssh_config1 new file mode 100644 index 00000000..719cc3ba --- /dev/null +++ b/tests/fixtures/generic/ssh_config1 @@ -0,0 +1,45 @@ +## override as per host ## +Host server1 + HostName server1.cyberciti.biz + User nixcraft + Port 4242 + IdentityFile /nfs/shared/users/nixcraft/keys/server1/id_rsa + +## Home nas server ## +Host nas01 + HostName 192.168.1.100 + User root + IdentityFile ~/.ssh/nas01.key + +## Login AWS Cloud ## +Host aws.apache + HostName 1.2.3.4 + User wwwdata + IdentityFile ~/.ssh/aws.apache.key + +## Login to internal lan server at 192.168.0.251 via our public uk office ssh based gateway using ## +## $ ssh uk.gw.lan ## +Host uk.gw.lan uk.lan + HostName 192.168.0.251 + User nixcraft + ProxyCommand ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null + +## Our Us Proxy Server ## +## Forward all local port 3128 traffic to port 3128 on the remote vps1.cyberciti.biz server ## +## $ ssh -f -N proxyus ## +Host proxyus + HostName vps1.cyberciti.biz + User breakfree + IdentityFile ~/.ssh/vps1.cyberciti.biz.key + LocalForward 3128 127.0.0.1:3128 + +### default for all ## +Host * + ForwardAgent no + ForwardX11 no + ForwardX11Trusted yes + User nixcraft + Port 22 + Protocol 2 + ServerAliveInterval 60 + ServerAliveCountMax 30 diff --git a/tests/fixtures/generic/ssh_config1.json b/tests/fixtures/generic/ssh_config1.json new file mode 100644 index 00000000..99cd39cf --- /dev/null +++ b/tests/fixtures/generic/ssh_config1.json @@ -0,0 +1 @@ +[{"host":"server1","host_list":["server1"],"hostname":"server1.cyberciti.biz","user":"nixcraft","port":4242,"identityfile":["/nfs/shared/users/nixcraft/keys/server1/id_rsa"]},{"host":"nas01","host_list":["nas01"],"hostname":"192.168.1.100","user":"root","identityfile":["~/.ssh/nas01.key"]},{"host":"aws.apache","host_list":["aws.apache"],"hostname":"1.2.3.4","user":"wwwdata","identityfile":["~/.ssh/aws.apache.key"]},{"host":"uk.gw.lan uk.lan","host_list":["uk.gw.lan","uk.lan"],"hostname":"192.168.0.251","user":"nixcraft","proxycommand":"ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"},{"host":"proxyus","host_list":["proxyus"],"hostname":"vps1.cyberciti.biz","user":"breakfree","identityfile":["~/.ssh/vps1.cyberciti.biz.key"],"localforward":["3128 127.0.0.1:3128"]},{"host":"*","host_list":["*"],"forwardagent":"no","forwardx11":"no","forwardx11trusted":"yes","user":"nixcraft","port":22,"protocol":2,"serveraliveinterval":60,"serveralivecountmax":30}] diff --git a/tests/fixtures/generic/ssh_config2 b/tests/fixtures/generic/ssh_config2 new file mode 100644 index 00000000..15261f36 --- /dev/null +++ b/tests/fixtures/generic/ssh_config2 @@ -0,0 +1,21 @@ +Host targaryen + HostName 192.168.1.10 + User daenerys + Port 7654 + IdentityFile ~/.ssh/targaryen.key + +Host tyrell + HostName 192.168.10.20 + +Host martell + HostName 192.168.10.50 + +Host *ell + user oberyn + +Host * !martell + LogLevel INFO + +Host * + User root + Compression yes diff --git a/tests/fixtures/generic/ssh_config2.json b/tests/fixtures/generic/ssh_config2.json new file mode 100644 index 00000000..3f2683a3 --- /dev/null +++ b/tests/fixtures/generic/ssh_config2.json @@ -0,0 +1 @@ +[{"host":"targaryen","host_list":["targaryen"],"hostname":"192.168.1.10","user":"daenerys","port":7654,"identityfile":["~/.ssh/targaryen.key"]},{"host":"tyrell","host_list":["tyrell"],"hostname":"192.168.10.20"},{"host":"martell","host_list":["martell"],"hostname":"192.168.10.50"},{"host":"*ell","host_list":["*ell"],"user":"oberyn"},{"host":"* !martell","host_list":["*","!martell"],"loglevel":"INFO"},{"host":"*","host_list":["*"],"user":"root","compression":"yes"}] diff --git a/tests/fixtures/generic/ssh_config3 b/tests/fixtures/generic/ssh_config3 new file mode 100644 index 00000000..8f2e41ed --- /dev/null +++ b/tests/fixtures/generic/ssh_config3 @@ -0,0 +1,33 @@ +Host server1 + HostName server1.cyberciti.biz + User nixcraft + Port 4242 + IdentityFile /nfs/shared/users/nixcraft/keys/server1/id_rsa + +## Home nas server ## +Host nas01 + HostName 192.168.1.100 + User root + IdentityFile ~/.ssh/nas01.key + +## Login AWS Cloud ## +Host aws.apache + HostName 1.2.3.4 + User wwwdata + IdentityFile ~/.ssh/aws.apache.key + +## Login to internal lan server at 192.168.0.251 via our public uk office ssh based gateway using ## +## $ ssh uk.gw.lan ## +Host uk.gw.lan uk.lan + HostName 192.168.0.251 + User nixcraft + ProxyCommand ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null + +## Our Us Proxy Server ## +## Forward all local port 3128 traffic to port 3128 on the remote vps1.cyberciti.biz server ## +## $ ssh -f -N proxyus ## +Host proxyus + HostName vps1.cyberciti.biz + User breakfree + IdentityFile ~/.ssh/vps1.cyberciti.biz.key + LocalForward 3128 127.0.0.1:3128 diff --git a/tests/fixtures/generic/ssh_config3.json b/tests/fixtures/generic/ssh_config3.json new file mode 100644 index 00000000..22d75a5f --- /dev/null +++ b/tests/fixtures/generic/ssh_config3.json @@ -0,0 +1 @@ +[{"host":"server1","host_list":["server1"],"hostname":"server1.cyberciti.biz","user":"nixcraft","port":4242,"identityfile":["/nfs/shared/users/nixcraft/keys/server1/id_rsa"]},{"host":"nas01","host_list":["nas01"],"hostname":"192.168.1.100","user":"root","identityfile":["~/.ssh/nas01.key"]},{"host":"aws.apache","host_list":["aws.apache"],"hostname":"1.2.3.4","user":"wwwdata","identityfile":["~/.ssh/aws.apache.key"]},{"host":"uk.gw.lan uk.lan","host_list":["uk.gw.lan","uk.lan"],"hostname":"192.168.0.251","user":"nixcraft","proxycommand":"ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"},{"host":"proxyus","host_list":["proxyus"],"hostname":"vps1.cyberciti.biz","user":"breakfree","identityfile":["~/.ssh/vps1.cyberciti.biz.key"],"localforward":["3128 127.0.0.1:3128"]}] diff --git a/tests/fixtures/generic/ssh_config4 b/tests/fixtures/generic/ssh_config4 new file mode 100644 index 00000000..318d5cb9 --- /dev/null +++ b/tests/fixtures/generic/ssh_config4 @@ -0,0 +1,105 @@ +Host * + AddKeysToAgent ask + AddressFamily inet + BatchMode no + BindAddress 1.1.1.1 + BindInterface en0 + CanonicalDomains abc.com xyz.com + CanonicalizeFallbackLocal yes + CanonicalizeHostname none + CanonicalizeMaxDots 2 + CanonicalizePermittedCNAMEs *.a.example.com:*.b.example.com,*.c.example.com + CASignatureAlgorithms ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ssh-ed25519@openssh.com + CertificateFile ~/certificates/cert1.pem + CertificateFile ~/certificates/cert2.pem + CheckHostIP yes + Ciphers 3des-cbc,aes128-cbc,aes192-cbc + ClearAllForwardings yes + Compression yes + ConnectionAttempts 9 + ConnectTimeout 30 + ControlMaster ask + ControlPath none + ControlPersist yes + DynamicForward 1.1.1.1:443 + EnableEscapeCommandline no + EnableSSHKeysign yes + EscapeChar none + ExitOnForwardFailure yes + FingerprintHash md5 + ForkAfterAuthentication yes + ForwardAgent $mypath + ForwardX11 no + ForwardX11Timeout 500 + ForwardX11Trusted yes + GatewayPorts yes + GlobalKnownHostsFile /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2 + GSSAPIAuthentication yes + GSSAPIDelegateCredentials yes + HashKnownHosts yes + HostbasedAcceptedAlgorithms ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com + HostbasedAuthentication yes + HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com + HostKeyAlias foobar + Hostname localhost + IdentitiesOnly yes + IdentityAgent SSH_AUTH_SOCK + IdentityFile ~/.ssh/vps1.cyberciti.biz.key + IdentityFile ~/.ssh/vps2.cyberciti.biz.key + IgnoreUnknown helloworld + Include ~/.ssh/config-extras ~/foo/bar + Include ~/.ssh/config-extra-extras + IPQoS af11 af12 + KbdInteractiveAuthentication yes + KbdInteractiveDevices bsdauth,pam,skey + KexAlgorithms +sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org + KnownHostsCommand ~/checkknownhosts + LocalCommand ~/mycommand + LocalForward 3128 127.0.0.1:3128 + LocalForward 3129 127.0.0.1:3129 + LogLevel INFO + LogVerbose kex.c:*:1000,*:kex_exchange_identification():*,packet.c:* + MACs ^umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com + NoHostAuthenticationForLocalhost yes + NumberOfPasswordPrompts 3 + PasswordAuthentication yes + PermitLocalCommand yes + PermitRemoteOpen 1.1.1.1:443 2.2.2.2:443 + PKCS11Provider ~/pkcs11provider + Port 22 + PreferredAuthentications gssapi-with-mic,hostbased,publickey,keyboard-interactive,password + Protocol 2 + ProxyCommand ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null + ProxyJump 1.1.1.1:22,2.2.2.2:22 + ProxyUseFdpass yes + PubkeyAcceptedAlgorithms -ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com + PubkeyAuthentication unbound + RekeyLimit 4G + RemoteCommand ~/mycommand + RemoteForward 1.1.1.1:22 2.2.2.2:22 + RequestTTY force + RequiredRSASize 2048 + RevokedHostKeys ~/revokedkeyfile + SecurityKeyProvider ~/keyprovider + SendEnv ENV1 ENV2 + SendEnv ENV3 + ServerAliveCountMax 3 + ServerAliveInterval 3 + SessionType none + SetEnv ENV1 ENV2 + SetEnv ENV3 + StdinNull yes + StreamLocalBindMask 0000 + StreamLocalBindUnlink yes + StrictHostKeyChecking ask + SyslogFacility USER + TCPKeepAlive yes + Tunnel ethernet + TunnelDevice tun1:tun2 + UpdateHostKeys ask + User nixcraft + UserKnownHostsFile ~/.ssh/knownhosts1 ~/.ssh/knownhosts2 + VerifyHostKeyDNS ask + VisualHostKey yes + XAuthLocation /usr/X11R6/bin/xauth + diff --git a/tests/fixtures/generic/ssh_config4.json b/tests/fixtures/generic/ssh_config4.json new file mode 100644 index 00000000..d3a8560e --- /dev/null +++ b/tests/fixtures/generic/ssh_config4.json @@ -0,0 +1 @@ +[{"host":"*","host_list":["*"],"addkeystoagent":"ask","addressfamily":"inet","batchmode":"no","bindaddress":"1.1.1.1","bindinterface":"en0","canonicaldomains":["abc.com","xyz.com"],"canonicalizefallbacklocal":"yes","canonicalizehostname":"none","canonicalizemaxdots":2,"canonicalizepermittedcnames":["*.a.example.com:*.b.example.com","*.c.example.com"],"casignaturealgorithms":["ssh-ed25519","ecdsa-sha2-nistp256","ecdsa-sha2-nistp384","ecdsa-sha2-nistp521","sk-ssh-ed25519@openssh.com"],"certificatefile":["~/certificates/cert1.pem","~/certificates/cert2.pem"],"checkhostip":"yes","ciphers":["3des-cbc","aes128-cbc","aes192-cbc"],"clearallforwardings":"yes","compression":"yes","connectionattempts":9,"connecttimeout":30,"controlmaster":"ask","controlpath":"none","controlpersist":"yes","dynamicforward":"1.1.1.1:443","enableescapecommandline":"no","enablesshkeysign":"yes","escapechar":"none","exitonforwardfailure":"yes","fingerprinthash":"md5","forkafterauthentication":"yes","forwardagent":"$mypath","forwardx11":"no","forwardx11timeout":500,"forwardx11trusted":"yes","gatewayports":"yes","globalknownhostsfile":["/etc/ssh/ssh_known_hosts","/etc/ssh/ssh_known_hosts2"],"gssapiauthentication":"yes","gssapidelegatecredentials":"yes","hashknownhosts":"yes","hostbasedacceptedalgorithms":["ssh-ed25519-cert-v01@openssh.com","ecdsa-sha2-nistp256-cert-v01@openssh.com"],"hostbasedauthentication":"yes","hostkeyalgorithms":["ssh-ed25519-cert-v01@openssh.com","ecdsa-sha2-nistp256-cert-v01@openssh.com"],"hostkeyalias":"foobar","hostname":"localhost","identitiesonly":"yes","identityagent":"SSH_AUTH_SOCK","identityfile":["~/.ssh/vps1.cyberciti.biz.key","~/.ssh/vps2.cyberciti.biz.key"],"ignoreunknown":"helloworld","include":["~/.ssh/config-extras","~/foo/bar","~/.ssh/config-extra-extras"],"ipqos":["af11","af12"],"kbdinteractiveauthentication":"yes","kbdinteractivedevices":["bsdauth","pam","skey"],"kexalgorithms":["sntrup761x25519-sha512@openssh.com","curve25519-sha256","curve25519-sha256@libssh.org"],"kexalgorithms_strategy":"+","knownhostscommand":"~/checkknownhosts","localcommand":"~/mycommand","localforward":["3128 127.0.0.1:3128","3129 127.0.0.1:3129"],"loglevel":"INFO","logverbose":["kex.c:*:1000","*:kex_exchange_identification():*","packet.c:*"],"macs":["umac-64-etm@openssh.com","umac-128-etm@openssh.com","hmac-sha2-256-etm@openssh.com","hmac-sha2-512-etm@openssh.com"],"macs_strategy":"^","nohostauthenticationforlocalhost":"yes","numberofpasswordprompts":3,"passwordauthentication":"yes","permitlocalcommand":"yes","permitremoteopen":["1.1.1.1:443","2.2.2.2:443"],"pkcs11provider":"~/pkcs11provider","port":22,"preferredauthentications":["gssapi-with-mic","hostbased","publickey","keyboard-interactive","password"],"protocol":2,"proxycommand":"ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null","proxyjump":["1.1.1.1:22","2.2.2.2:22"],"proxyusefdpass":"yes","pubkeyacceptedalgorithms":["ssh-ed25519-cert-v01@openssh.com","ecdsa-sha2-nistp256-cert-v01@openssh.com"],"pubkeyacceptedalgorithms_strategy":"-","pubkeyauthentication":"unbound","rekeylimit":"4G","remotecommand":"~/mycommand","remoteforward":"1.1.1.1:22 2.2.2.2:22","requesttty":"force","requiredrsasize":2048,"revokedhostkeys":"~/revokedkeyfile","securitykeyprovider":"~/keyprovider","sendenv":["ENV1","ENV2","ENV3"],"serveralivecountmax":3,"serveraliveinterval":3,"sessiontype":"none","setenv":["ENV1","ENV2","ENV3"],"stdinnull":"yes","streamlocalbindmask":"0000","streamlocalbindunlink":"yes","stricthostkeychecking":"ask","syslogfacility":"USER","tcpkeepalive":"yes","tunnel":"ethernet","tunneldevice":"tun1:tun2","updatehostkeys":"ask","user":"nixcraft","userknownhostsfile":["~/.ssh/knownhosts1","~/.ssh/knownhosts2"],"verifyhostkeydns":"ask","visualhostkey":"yes","xauthlocation":"/usr/X11R6/bin/xauth"}] diff --git a/tests/fixtures/generic/ssh_config5 b/tests/fixtures/generic/ssh_config5 new file mode 100644 index 00000000..f24ac73d --- /dev/null +++ b/tests/fixtures/generic/ssh_config5 @@ -0,0 +1,14 @@ + +# comment +Host * + User something + +# comment 2 +Host svu + Hostname www.svuniversity.ac.in + # within-host-comment + Port 22 + ProxyCommand nc -w 300 -x localhost:9050 %h %p + +# another comment +# bla bla diff --git a/tests/fixtures/generic/ssh_config5.json b/tests/fixtures/generic/ssh_config5.json new file mode 100644 index 00000000..35db7059 --- /dev/null +++ b/tests/fixtures/generic/ssh_config5.json @@ -0,0 +1 @@ +[{"host":"*","host_list":["*"],"user":"something"},{"host":"svu","host_list":["svu"],"hostname":"www.svuniversity.ac.in","port":22,"proxycommand":"nc -w 300 -x localhost:9050 %h %p"}] diff --git a/tests/test_ssh_conf.py b/tests/test_ssh_conf.py new file mode 100644 index 00000000..aa6647b1 --- /dev/null +++ b/tests/test_ssh_conf.py @@ -0,0 +1,95 @@ +import os +import unittest +import json +from typing import Dict +from jc.parsers.ssh_conf import parse + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class MyTests(unittest.TestCase): + f_in: Dict = {} + f_json: Dict = {} + + @classmethod + def setUpClass(cls): + fixtures = { + 'ssh_config1': ( + 'fixtures/generic/ssh_config1', + 'fixtures/generic/ssh_config1.json'), + 'ssh_config2': ( + 'fixtures/generic/ssh_config2', + 'fixtures/generic/ssh_config2.json'), + 'ssh_config3': ( + 'fixtures/generic/ssh_config3', + 'fixtures/generic/ssh_config3.json'), + 'ssh_config4': ( + 'fixtures/generic/ssh_config4', + 'fixtures/generic/ssh_config4.json'), + 'ssh_config5': ( + 'fixtures/generic/ssh_config5', + 'fixtures/generic/ssh_config5.json') + } + + for file, filepaths in fixtures.items(): + with open(os.path.join(THIS_DIR, filepaths[0]), 'r', encoding='utf-8') as a, \ + open(os.path.join(THIS_DIR, filepaths[1]), 'r', encoding='utf-8') as b: + cls.f_in[file] = a.read() + cls.f_json[file] = json.loads(b.read()) + + + def test_ssh_nodata(self): + """ + Test 'ssh' with no data + """ + self.assertEqual(parse('', quiet=True), []) + + + def test_ssh_config1(self): + """ + Test 'ssh' config 1 + """ + self.assertEqual( + parse(self.f_in['ssh_config1'], quiet=True), + self.f_json['ssh_config1'] + ) + + def test_ssh_config2(self): + """ + Test 'ssh' config 2 + """ + self.assertEqual( + parse(self.f_in['ssh_config2'], quiet=True), + self.f_json['ssh_config2'] + ) + + def test_ssh_config3(self): + """ + Test 'ssh' config 3 + """ + self.assertEqual( + parse(self.f_in['ssh_config3'], quiet=True), + self.f_json['ssh_config3'] + ) + + def test_ssh_config4(self): + """ + Test 'ssh' config 4 + """ + self.assertEqual( + parse(self.f_in['ssh_config4'], quiet=True), + self.f_json['ssh_config4'] + ) + + def test_ssh_config5(self): + """ + Test 'ssh' config 5 + """ + self.assertEqual( + parse(self.f_in['ssh_config5'], quiet=True), + self.f_json['ssh_config5'] + ) + + +if __name__ == '__main__': + unittest.main()