Get rid of another place where listen on port 0, and switch to FilePath only for now.

This commit is contained in:
Itamar Turner-Trauring 2022-04-06 11:25:37 -04:00
parent 710fad4f8a
commit eda5925548
3 changed files with 47 additions and 29 deletions

View File

@ -3,7 +3,6 @@ HTTP server for storage.
"""
from typing import Dict, List, Set, Tuple, Any
from pathlib import Path
from functools import wraps
from base64 import b64decode
@ -17,6 +16,8 @@ from twisted.internet.defer import Deferred
from twisted.internet.ssl import CertificateOptions, Certificate, PrivateCertificate
from twisted.web.server import Site
from twisted.protocols.tls import TLSMemoryBIOFactory
from twisted.python.filepath import FilePath
import attr
from werkzeug.http import (
parse_range_header,
@ -524,14 +525,31 @@ class HTTPServer(object):
@attr.s
class _TLSEndpointWrapper(object):
"""
Wrap an existing endpoint with the storage TLS policy. This is useful
because not all Tahoe-LAFS endpoints might be plain TCP+TLS, for example
there's Tor and i2p.
Wrap an existing endpoint with the server-side storage TLS policy. This is
useful because not all Tahoe-LAFS endpoints might be plain TCP+TLS, for
example there's Tor and i2p.
"""
endpoint = attr.ib(type=IStreamServerEndpoint)
context_factory = attr.ib(type=CertificateOptions)
@classmethod
def from_paths(
cls, endpoint, private_key_path: FilePath, cert_path: FilePath
) -> "_TLSEndpointWrapper":
"""
Create an endpoint with the given private key and certificate paths on
the filesystem.
"""
certificate = Certificate.loadPEM(cert_path.getContent()).original
private_key = PrivateCertificate.loadPEM(
cert_path.getContent() + b"\n" + private_key_path.getContent()
).privateKey.original
certificate_options = CertificateOptions(
privateKey=private_key, certificate=certificate
)
return cls(endpoint=endpoint, context_factory=certificate_options)
def listen(self, factory):
return self.endpoint.listen(
TLSMemoryBIOFactory(self.context_factory, False, factory)
@ -542,8 +560,8 @@ def listen_tls(
server: HTTPServer,
hostname: str,
endpoint: IStreamServerEndpoint,
private_key_path: Path,
cert_path: Path,
private_key_path: FilePath,
cert_path: FilePath,
) -> Deferred[Tuple[DecodedURL, IListeningPort]]:
"""
Start a HTTPS storage server on the given port, return the NURL and the
@ -555,13 +573,7 @@ def listen_tls(
This will likely need to be updated eventually to handle Tor/i2p.
"""
certificate = Certificate.loadPEM(cert_path.read_bytes()).original
private_key = PrivateCertificate.loadPEM(
cert_path.read_bytes() + b"\n" + private_key_path.read_bytes()
).privateKey.original
endpoint = _TLSEndpointWrapper(
endpoint, CertificateOptions(privateKey=private_key, certificate=certificate)
)
endpoint = _TLSEndpointWrapper.from_paths(endpoint, private_key_path, cert_path)
def build_nurl(listening_port: IListeningPort) -> DecodedURL:
nurl = DecodedURL().replace(
@ -571,7 +583,7 @@ def listen_tls(
path=(str(server._swissnum, "ascii"),),
userinfo=(
str(
get_spki_hash(load_pem_x509_certificate(cert_path.read_bytes())),
get_spki_hash(load_pem_x509_certificate(cert_path.getContent())),
"ascii",
),
),

View File

@ -14,12 +14,12 @@ from typing import Set
from random import Random
from unittest import SkipTest
from pathlib import Path
from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twisted.internet.task import Clock
from twisted.internet import reactor
from twisted.internet.endpoints import serverFromString
from twisted.python.filepath import FilePath
from foolscap.api import Referenceable, RemoteException
from allmydata.interfaces import IStorageServer # really, IStorageClient
@ -1059,7 +1059,7 @@ class _HTTPMixin(_SharedMixin):
# Listen on randomly assigned port, using self-signed cert we generated
# manually:
certs_dir = Path(__file__).parent / "certs"
certs_dir = FilePath(__file__).parent().child("certs")
_, endpoint_string = self._port_assigner.assign(reactor)
nurl, listening_port = yield listen_tls(
http_storage_server,
@ -1069,8 +1069,8 @@ class _HTTPMixin(_SharedMixin):
# private key; nothing at all special about it. You can regenerate
# with code in allmydata.test.test_storage_https or with openssl
# CLI, with no meaningful change to the test.
certs_dir / "private.key",
certs_dir / "domain.crt",
certs_dir.child("private.key"),
certs_dir.child("domain.crt"),
)
self.addCleanup(listening_port.stopListening)

View File

@ -15,18 +15,20 @@ from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization, hashes
from twisted.internet.endpoints import quoteStringArgument, serverFromString
from twisted.internet.endpoints import serverFromString
from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.internet.task import deferLater
from twisted.web.server import Site
from twisted.web.static import Data
from twisted.web.client import Agent, HTTPConnectionPool, ResponseNeverReceived
from twisted.python.filepath import FilePath
from treq.client import HTTPClient
from .common import SyncTestCase, AsyncTestCase
from .common import SyncTestCase, AsyncTestCase, SameProcessStreamEndpointAssigner
from ..storage.http_common import get_spki_hash
from ..storage.http_client import _StorageClientHTTPSPolicy
from ..storage.http_server import _TLSEndpointWrapper
class HTTPSNurlTests(SyncTestCase):
@ -90,7 +92,13 @@ class PinningHTTPSValidation(AsyncTestCase):
https://cryptography.io/en/latest/x509/tutorial/#creating-a-self-signed-certificate
"""
def to_file(self, key_or_cert) -> str:
def setUp(self):
self._port_assigner = SameProcessStreamEndpointAssigner()
self._port_assigner.setUp()
self.addCleanup(self._port_assigner.tearDown)
return AsyncTestCase.setUp(self)
def to_file(self, key_or_cert) -> FilePath:
"""
Write the given key or cert to a temporary file on disk, return the
path.
@ -106,7 +114,7 @@ class PinningHTTPSValidation(AsyncTestCase):
encryption_algorithm=serialization.NoEncryption(),
)
f.write(data)
return path
return FilePath(path)
def generate_private_key(self):
"""Create a RSA private key."""
@ -137,19 +145,17 @@ class PinningHTTPSValidation(AsyncTestCase):
)
@asynccontextmanager
async def listen(self, private_key_path, cert_path):
async def listen(self, private_key_path: FilePath, cert_path: FilePath):
"""
Context manager that runs a HTTPS server with the given private key
and certificate.
Returns a URL that will connect to the server.
"""
endpoint = serverFromString(
reactor,
"ssl:privateKey={}:certKey={}:port=0:interface=127.0.0.1".format(
quoteStringArgument(str(private_key_path)),
quoteStringArgument(str(cert_path)),
),
location_hint, endpoint_string = self._port_assigner.assign(reactor)
underlying_endpoint = serverFromString(reactor, endpoint_string)
endpoint = _TLSEndpointWrapper.from_paths(
underlying_endpoint, private_key_path, cert_path
)
root = Data(b"YOYODYNE", "text/plain")
root.isLeaf = True