Skip to content

Commit

Permalink
Add ssh-fingerprint check: `Verifies if remote host fingerprint mat…
Browse files Browse the repository at this point in the history
…ches. Helps detecting man-in-the-middle and server takeover attacks.`
  • Loading branch information
blackandred committed Oct 26, 2019
1 parent 4a97f80 commit fd2c9d1
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 0 deletions.
56 changes: 56 additions & 0 deletions infracheck/checks/ssh-fingerprint
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python3

"""
ssh-fingerprint
---------------
Verifies if remote host fingerprint matches. Helps detecting man-in-the-middle and server takeover attacks.
Parameters:
- EXPECTED_FINGERPRINT (example: zsp.net.pl ssh-rsa SOMESOMESOMESOMESOMEKEYHERE)
- METHOD (default: rsa)
- HOST (example: zsp.net.pl)
- PORT (example: 22)
"""

import subprocess
import os
import sys


class SshFingerprintScan:
def main(self, expected_fingerprint: str, method: str, domain: str, port: int) -> tuple:
if expected_fingerprint == '':
return 'You need to provide a EXPECTED_FINGERPRINT', False

if method == '':
return 'You need to provide a METHOD', False

if domain == '':
return 'You need to provide a HOST', False

fingerprint = self._get_fingerprint(domain, method, port)

if fingerprint.strip() == expected_fingerprint.strip():
return "Fingerprint is OK", True

return "Fingerprint does not match. Expected: %s, got: %s" % (expected_fingerprint, fingerprint), False

@staticmethod
def _get_fingerprint(domain: str, method: str, port: int) -> str:
out = subprocess.check_output(['ssh-keyscan', '-t', method, '-p', str(port), domain], stderr=subprocess.DEVNULL)
return out.decode('utf-8')


if __name__ == '__main__':
app = SshFingerprintScan()
text, status = app.main(
expected_fingerprint=os.getenv('EXPECTED_FINGERPRINT', ''),
method=os.getenv('METHOD', 'rsa'),
domain=os.getenv('HOST', ''),
port=int(os.getenv('PORT', 22))
)

print(text)
sys.exit(0 if status else 1)
50 changes: 50 additions & 0 deletions tests/functional_test_ssh_fingerprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import unittest
import docker

from .utils import run_check
from .utils.ssh_test import TestThatRequiresSshServer


class SshFingerprintTest(TestThatRequiresSshServer, unittest.TestCase):
docker_client: docker.DockerClient

def test_success_case(self):
stdout: str
result: int

# we create SSH server for testing dynamically in a docker container, so each time it has a different identity
current_expected_fingerprint = self.get_current_ssh_server_fingerprint()

stdout, result, hooks_output = run_check('ssh-fingerprint', {
'HOST': 'localhost',
'PORT': 3222,
'EXPECTED_FINGERPRINT': current_expected_fingerprint.decode('utf-8')
}, {})

self.assertEqual('Fingerprint is OK', stdout.strip())
self.assertTrue(result)

def test_invalid_fingerprint(self):
stdout: str
result: int

stdout, result, hooks_output = run_check('ssh-fingerprint', {
'HOST': 'localhost',
'PORT': 3222,
'EXPECTED_FINGERPRINT': 'SOME FINGERPRINT THAT DOES NOT MATCH SERVER FINGERPRINT'
}, {})

self.assertIn('Fingerprint does not match', stdout.strip())
self.assertFalse(result)

def test_missing_host_parameter(self):
stdout: str
result: int

stdout, result, hooks_output = run_check('ssh-fingerprint', {
'PORT': 3222,
'EXPECTED_FINGERPRINT': 'SOME FINGERPRINT THAT DOES NOT MATCH SERVER FINGERPRINT'
}, {})

self.assertIn('You need to provide a HOST', stdout.strip())
self.assertFalse(result)

0 comments on commit fd2c9d1

Please sign in to comment.