Hook up NURL generation to the new Foolscap/HTTPS protocol switch.

This commit is contained in:
Itamar Turner-Trauring 2022-07-20 15:12:00 -04:00
parent 5e0c32708b
commit 11f4ebc0d9
4 changed files with 52 additions and 51 deletions

View File

@ -37,6 +37,7 @@ import allmydata
from allmydata.crypto import rsa, ed25519
from allmydata.crypto.util import remove_prefix
from allmydata.storage.server import StorageServer, FoolscapStorageServer
from allmydata.storage.http_server import build_nurl
from allmydata import storage_client
from allmydata.immutable.upload import Uploader
from allmydata.immutable.offloaded import Helper
@ -658,6 +659,12 @@ class _Client(node.Node, pollmixin.PollMixin):
if webport:
self.init_web(webport) # strports string
# TODO this may be the wrong location for now? but as temporary measure
# it allows us to get NURLs for testing in test_istorageserver.py Will
# eventually get fixed one way or another in
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3901
self.storage_nurls = []
def init_stats_provider(self):
self.stats_provider = StatsProvider(self)
self.stats_provider.setServiceParent(self)
@ -820,6 +827,15 @@ class _Client(node.Node, pollmixin.PollMixin):
furl = self.tub.registerReference(FoolscapStorageServer(ss), furlFile=furl_file)
(_, _, swissnum) = furl.rpartition("/")
self.tub.negotiationClass.add_storage_server(ss, swissnum.encode("ascii"))
for location_hint in self.tub.locationHints:
if location_hint.startswith("tcp:"):
_, hostname, port = location_hint.split(":")
port = int(port)
self.storage_nurls.append(
build_nurl(
hostname, port, swissnum, self.tub.myCertificate.original.to_cryptography()
)
)
announcement["anonymous-storage-FURL"] = furl

View File

@ -66,14 +66,6 @@ class _FoolscapOrHttps(Protocol, metaclass=_PretendToBeNegotiation):
Add the various storage server-related attributes needed by a
``Tub``-specific ``_FoolscapOrHttps`` subclass.
"""
# TODO tub.locationHints will be in the format ["tcp:hostname:port"]
# (and maybe some other things we can ignore for now). We also have
# access to the certificate. Together, this should be sufficient to
# construct NURLs, one per hint. The code for NURls should be
# refactored out of http_server.py's build_nurl; that code might want
# to skip around for the future when we don't do foolscap, but for now
# this module will be main way we set up HTTPS.
# Tub.myCertificate is a twisted.internet.ssl.PrivateCertificate
# instance.
certificate_options = CertificateOptions(

View File

@ -10,6 +10,7 @@ from base64 import b64decode
import binascii
from tempfile import TemporaryFile
from cryptography.x509 import Certificate
from zope.interface import implementer
from klein import Klein
from twisted.web import http
@ -843,6 +844,29 @@ class _TLSEndpointWrapper(object):
)
def build_nurl(
hostname: str, port: int, swissnum: str, certificate: Certificate
) -> DecodedURL:
"""
Construct a HTTPS NURL, given the hostname, port, server swissnum, and x509
certificate for the server. Clients can then connect to the server using
this NURL.
"""
return DecodedURL().replace(
fragment="v=1", # how we know this NURL is HTTP-based (i.e. not Foolscap)
host=hostname,
port=port,
path=(swissnum,),
userinfo=(
str(
get_spki_hash(certificate),
"ascii",
),
),
scheme="pb",
)
def listen_tls(
server: HTTPServer,
hostname: str,
@ -862,22 +886,14 @@ def listen_tls(
"""
endpoint = _TLSEndpointWrapper.from_paths(endpoint, private_key_path, cert_path)
def build_nurl(listening_port: IListeningPort) -> DecodedURL:
nurl = DecodedURL().replace(
fragment="v=1", # how we know this NURL is HTTP-based (i.e. not Foolscap)
host=hostname,
port=listening_port.getHost().port,
path=(str(server._swissnum, "ascii"),),
userinfo=(
str(
get_spki_hash(load_pem_x509_certificate(cert_path.getContent())),
"ascii",
),
),
scheme="pb",
def get_nurl(listening_port: IListeningPort) -> DecodedURL:
return build_nurl(
hostname,
listening_port.getHost().port,
str(server._swissnum, "ascii"),
load_pem_x509_certificate(cert_path.getContent()),
)
return nurl
return endpoint.listen(Site(server.get_resource())).addCallback(
lambda listening_port: (build_nurl(listening_port), listening_port)
lambda listening_port: (get_nurl(listening_port), listening_port)
)

View File

@ -1084,40 +1084,17 @@ class _FoolscapMixin(_SharedMixin):
class _HTTPMixin(_SharedMixin):
"""Run tests on the HTTP version of ``IStorageServer``."""
def setUp(self):
self._port_assigner = SameProcessStreamEndpointAssigner()
self._port_assigner.setUp()
self.addCleanup(self._port_assigner.tearDown)
return _SharedMixin.setUp(self)
@inlineCallbacks
def _get_istorage_server(self):
swissnum = b"1234"
http_storage_server = HTTPServer(self.server, swissnum)
# Listen on randomly assigned port, using self-signed cert:
private_key = generate_private_key()
certificate = generate_certificate(private_key)
_, endpoint_string = self._port_assigner.assign(reactor)
nurl, listening_port = yield listen_tls(
http_storage_server,
"127.0.0.1",
serverFromString(reactor, endpoint_string),
private_key_to_file(FilePath(self.mktemp()), private_key),
cert_to_file(FilePath(self.mktemp()), certificate),
)
self.addCleanup(listening_port.stopListening)
nurl = self.clients[0].storage_nurls[0]
# Create HTTP client with non-persistent connections, so we don't leak
# state across tests:
returnValue(
_HTTPStorageServer.from_http_client(
StorageClient.from_nurl(nurl, reactor, persistent=False)
)
client: IStorageServer = _HTTPStorageServer.from_http_client(
StorageClient.from_nurl(nurl, reactor, persistent=False)
)
self.assertTrue(IStorageServer.providedBy(client))
# Eventually should also:
# self.assertTrue(IStorageServer.providedBy(client))
return succeed(client)
class FoolscapSharedAPIsTests(