Commit fe55b022 authored by Jérome Perrin's avatar Jérome Perrin

seleniumserver: publish ssh fingerprint

so that on first connection clients can check the fingerprint key
instead of blindly accepting it.
parent 381f49e3
...@@ -19,4 +19,4 @@ md5sum = c4ac5de141ae6a64848309af03e51d88 ...@@ -19,4 +19,4 @@ md5sum = c4ac5de141ae6a64848309af03e51d88
[template-selenium] [template-selenium]
filename = instance-selenium.cfg.in filename = instance-selenium.cfg.in
md5sum = 4167621b473f81892d38389ac427c6ba md5sum = 8e6b9629c1489559ec88a67d7b1bdf41
...@@ -176,6 +176,19 @@ extra-args=-t dsa ...@@ -176,6 +176,19 @@ extra-args=-t dsa
<=ssh-keygen-base <=ssh-keygen-base
extra-args=-t ecdsa -b 521 extra-args=-t ecdsa -b 521
[ssh-key-fingerprint-command]
recipe = plone.recipe.command
# recent openssh client display ECDSA key's fingerprint as SHA256
command = ${openssh-output:keygen} -lf $${ssh-host-ecdsa-key:output}
[ssh-key-fingerprint]
recipe = collective.recipe.shelloutput
# XXX because collective.recipe.shelloutput ignore errors, we run the same
# command in a plone.recipe.command so that if fails if something goes wrong.
commands =
fingerprint = $${ssh-key-fingerprint-command:command}
[sshd-config] [sshd-config]
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
rendered = $${directory:etc}/sshd.conf rendered = $${directory:etc}/sshd.conf
...@@ -268,6 +281,7 @@ url = $${selenium-server-frontend-instance:url} ...@@ -268,6 +281,7 @@ url = $${selenium-server-frontend-instance:url}
admin-url = $${selenium-server-frontend-instance:admin-url} admin-url = $${selenium-server-frontend-instance:admin-url}
ssh-url = $${sshd-service:url} ssh-url = $${sshd-service:url}
ssh-fingerprint = $${ssh-key-fingerprint:fingerprint}
# to run a local node - useful to see what tests are doing or # to run a local node - useful to see what tests are doing or
# using to use unsupported browsers like safari or edge or to test # using to use unsupported browsers like safari or edge or to test
# on mobile using appium. # on mobile using appium.
......
...@@ -16,8 +16,12 @@ extends = ...@@ -16,8 +16,12 @@ extends =
./buildout.hash.cfg ./buildout.hash.cfg
parts = parts =
slapos-cookbook slapos-cookbook
template collective.recipe.shelloutput
template
[collective.recipe.shelloutput]
recipe = zc.recipe.egg
[selenium-server] [selenium-server]
recipe = slapos.recipe.build:download recipe = slapos.recipe.build:download
...@@ -41,4 +45,5 @@ output = ${buildout:directory}/template-selenium.cfg ...@@ -41,4 +45,5 @@ output = ${buildout:directory}/template-selenium.cfg
[versions] [versions]
plone.recipe.command = 1.1 plone.recipe.command = 1.1
collective.recipe.shelloutput = 0.1
slapos.recipe.template = 4.3 slapos.recipe.template = 4.3
...@@ -32,6 +32,9 @@ import os ...@@ -32,6 +32,9 @@ import os
import tempfile import tempfile
import unittest import unittest
import urlparse import urlparse
import base64
import hashlib
import contextlib
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from io import BytesIO from io import BytesIO
...@@ -343,20 +346,46 @@ class TestSSHServer(SeleniumServerTestCase): ...@@ -343,20 +346,46 @@ class TestSSHServer(SeleniumServerTestCase):
self.assertEqual('ssh', parsed.scheme) self.assertEqual('ssh', parsed.scheme)
client = paramiko.SSHClient() client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.client.WarningPolicy) class TestKeyPolicy(object):
client.connect( """Accept server key and keep it in self.key for inspection
username=urlparse.urlparse(ssh_url).username, """
hostname=urlparse.urlparse(ssh_url).hostname, def missing_host_key(self, client, hostname, key):
port=urlparse.urlparse(ssh_url).port, self.key = key
pkey=self.ssh_key, key_policy = TestKeyPolicy()
) client.set_missing_host_key_policy(key_policy)
channel = client.invoke_shell()
channel.settimeout(30) with contextlib.closing(client):
# apparently we sometimes need to send something on the first ssh connection client.connect(
channel.send('\n') username=urlparse.urlparse(ssh_url).username,
# openssh prints a warning 'Attempt to write login records by non-root user (aborting)' hostname=urlparse.urlparse(ssh_url).hostname,
# so we received more than the lenght of the asserted message. port=urlparse.urlparse(ssh_url).port,
self.assertIn("Welcome to SlapOS Selenium Server.", channel.recv(100)) pkey=self.ssh_key,
)
# Check fingerprint from server matches the published one.
# The publish format is the raw output of ssh-keygen and is something like this:
# 521 SHA256:9aZruv3LmFizzueIFdkd78eGtzghDoPSCBXFkkrHqXE user@hostname (ECDSA)
# we only want to parse SHA256:9aZruv3LmFizzueIFdkd78eGtzghDoPSCBXFkkrHqXE
_, fingerprint_string, _, key_type = parameter_dict['ssh-fingerprint'].split()
self.assertEqual(key_type, '(ECDSA)')
fingerprint_algorithm, fingerprint = fingerprint_string.split(':', 1)
self.assertEqual(fingerprint_algorithm, 'SHA256')
# Paramiko does not allow to get the fingerprint as SHA256 easily yet
# https://github.com/paramiko/paramiko/pull/1103
self.assertEqual(
fingerprint,
# XXX with sha256, we need to remove that trailing =
base64.b64encode(hashlib.new(fingerprint_algorithm, key_policy.key.asbytes()).digest())[:-1]
)
channel = client.invoke_shell()
channel.settimeout(30)
# apparently we sometimes need to send something on the first ssh connection
channel.send('\n')
# openssh prints a warning 'Attempt to write login records by non-root user (aborting)'
# so we received more than the lenght of the asserted message.
self.assertIn("Welcome to SlapOS Selenium Server.", channel.recv(100))
class TestFirefox52(BrowserCompatibilityMixin, SeleniumServerTestCase): class TestFirefox52(BrowserCompatibilityMixin, SeleniumServerTestCase):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment