mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-08 22:12:43 +00:00
wip; updates to grid-manager impl
This commit is contained in:
parent
64eb9d7c30
commit
ee3e1cbcf2
@ -15,6 +15,7 @@ from twisted.application.internet import TimerService
|
|||||||
from twisted.python.filepath import FilePath
|
from twisted.python.filepath import FilePath
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
from pycryptopp.publickey import rsa
|
from pycryptopp.publickey import rsa
|
||||||
|
from pycryptopp.publickey import ed25519
|
||||||
|
|
||||||
import allmydata
|
import allmydata
|
||||||
from allmydata.storage.server import StorageServer
|
from allmydata.storage.server import StorageServer
|
||||||
@ -31,6 +32,7 @@ from allmydata.util.abbreviate import parse_abbreviated_size
|
|||||||
from allmydata.util.time_format import parse_duration, parse_date
|
from allmydata.util.time_format import parse_duration, parse_date
|
||||||
from allmydata.util.i2p_provider import create as create_i2p_provider
|
from allmydata.util.i2p_provider import create as create_i2p_provider
|
||||||
from allmydata.util.tor_provider import create as create_tor_provider
|
from allmydata.util.tor_provider import create as create_tor_provider
|
||||||
|
from allmydata.util.base32 import a2b, b2a
|
||||||
from allmydata.stats import StatsProvider
|
from allmydata.stats import StatsProvider
|
||||||
from allmydata.history import History
|
from allmydata.history import History
|
||||||
from allmydata.interfaces import IStatsProducer, SDMF_VERSION, MDMF_VERSION, DEFAULT_MAX_SEGMENT_SIZE
|
from allmydata.interfaces import IStatsProducer, SDMF_VERSION, MDMF_VERSION, DEFAULT_MAX_SEGMENT_SIZE
|
||||||
@ -58,6 +60,7 @@ def _valid_config_sections():
|
|||||||
"shares.needed",
|
"shares.needed",
|
||||||
"shares.total",
|
"shares.total",
|
||||||
"stats_gatherer.furl",
|
"stats_gatherer.furl",
|
||||||
|
"grid_managers",
|
||||||
),
|
),
|
||||||
"drop_upload": ( # deprecated already?
|
"drop_upload": ( # deprecated already?
|
||||||
"enabled",
|
"enabled",
|
||||||
@ -380,10 +383,28 @@ def create_storage_farm_broker(config, default_connection_handlers, foolscap_con
|
|||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# grid manager setup
|
||||||
|
|
||||||
|
grid_manager_keys = []
|
||||||
|
gm_keydata = self.get_config('client', 'grid_manager_public_keys', '')
|
||||||
|
for gm_key in gm_keydata.strip().split():
|
||||||
|
grid_manager_keys.append(
|
||||||
|
keyutil.parse_pubkey(a2b(gm_key))
|
||||||
|
)
|
||||||
|
|
||||||
|
my_pubkey = keyutil.parse_pubkey(
|
||||||
|
self.get_config_from_file("node.pubkey")
|
||||||
|
)
|
||||||
|
|
||||||
|
# create the actual storage-broker
|
||||||
|
|
||||||
sb = storage_client.StorageFarmBroker(
|
sb = storage_client.StorageFarmBroker(
|
||||||
permute_peers=True,
|
permute_peers=True,
|
||||||
tub_maker=tub_creator,
|
tub_maker=tub_creator,
|
||||||
preferred_peers=preferred_peers,
|
preferred_peers=preferred_peers,
|
||||||
|
grid_manager_keys=grid_manager_keys,
|
||||||
|
node_pubkey=my_pubkey,
|
||||||
|
|
||||||
)
|
)
|
||||||
for ic in introducer_clients:
|
for ic in introducer_clients:
|
||||||
sb.use_introducer(ic)
|
sb.use_introducer(ic)
|
||||||
|
@ -515,7 +515,7 @@ class Tahoe2ServerSelector(log.PrefixingLogMixin):
|
|||||||
# 0. Start with an ordered list of servers. Maybe *2N* of them.
|
# 0. Start with an ordered list of servers. Maybe *2N* of them.
|
||||||
#
|
#
|
||||||
|
|
||||||
all_servers = storage_broker.get_servers_for_psi(storage_index)
|
all_servers = storage_broker.get_servers_for_psi(storage_index, for_upload=True)
|
||||||
if not all_servers:
|
if not all_servers:
|
||||||
raise NoServersError("client gave us zero servers")
|
raise NoServersError("client gave us zero servers")
|
||||||
|
|
||||||
|
@ -29,7 +29,11 @@ the foolscap-based server implemented in src/allmydata/storage/*.py .
|
|||||||
# 6: implement other sorts of IStorageClient classes: S3, etc
|
# 6: implement other sorts of IStorageClient classes: S3, etc
|
||||||
|
|
||||||
|
|
||||||
import re, time, hashlib
|
import re
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.application import service
|
from twisted.application import service
|
||||||
@ -67,12 +71,14 @@ class StorageFarmBroker(service.MultiService):
|
|||||||
I'm also responsible for subscribing to the IntroducerClient to find out
|
I'm also responsible for subscribing to the IntroducerClient to find out
|
||||||
about new servers as they are announced by the Introducer.
|
about new servers as they are announced by the Introducer.
|
||||||
"""
|
"""
|
||||||
def __init__(self, permute_peers, tub_maker, preferred_peers=()):
|
def __init__(self, permute_peers, tub_maker, preferred_peers=(), grid_manager_keys=[], node_pubkey=None):
|
||||||
service.MultiService.__init__(self)
|
service.MultiService.__init__(self)
|
||||||
assert permute_peers # False not implemented yet
|
assert permute_peers # False not implemented yet
|
||||||
self.permute_peers = permute_peers
|
self.permute_peers = permute_peers
|
||||||
self._tub_maker = tub_maker
|
self._tub_maker = tub_maker
|
||||||
self.preferred_peers = preferred_peers
|
self.preferred_peers = preferred_peers
|
||||||
|
self._grid_manager_keys = grid_manager_keys
|
||||||
|
self._node_pubkey = node_pubkey
|
||||||
|
|
||||||
# self.servers maps serverid -> IServer, and keeps track of all the
|
# self.servers maps serverid -> IServer, and keeps track of all the
|
||||||
# storage servers that we've heard about. Each descriptor manages its
|
# storage servers that we've heard about. Each descriptor manages its
|
||||||
@ -91,7 +97,7 @@ class StorageFarmBroker(service.MultiService):
|
|||||||
self._static_server_ids.add(server_id)
|
self._static_server_ids.add(server_id)
|
||||||
handler_overrides = server.get("connections", {})
|
handler_overrides = server.get("connections", {})
|
||||||
s = NativeStorageServer(server_id, server["ann"],
|
s = NativeStorageServer(server_id, server["ann"],
|
||||||
self._tub_maker, handler_overrides)
|
self._tub_maker, handler_overrides, [], self._node_pubkey)
|
||||||
s.on_status_changed(lambda _: self._got_connection())
|
s.on_status_changed(lambda _: self._got_connection())
|
||||||
s.setServiceParent(self)
|
s.setServiceParent(self)
|
||||||
self.servers[server_id] = s
|
self.servers[server_id] = s
|
||||||
@ -110,7 +116,7 @@ class StorageFarmBroker(service.MultiService):
|
|||||||
|
|
||||||
# these two are used in unit tests
|
# these two are used in unit tests
|
||||||
def test_add_rref(self, serverid, rref, ann):
|
def test_add_rref(self, serverid, rref, ann):
|
||||||
s = NativeStorageServer(serverid, ann.copy(), self._tub_maker, {})
|
s = NativeStorageServer(serverid, ann.copy(), self._tub_maker, {}, [], self._node_pubkey)
|
||||||
s.rref = rref
|
s.rref = rref
|
||||||
s._is_connected = True
|
s._is_connected = True
|
||||||
self.servers[serverid] = s
|
self.servers[serverid] = s
|
||||||
@ -151,7 +157,7 @@ class StorageFarmBroker(service.MultiService):
|
|||||||
facility="tahoe.storage_broker", umid="AlxzqA",
|
facility="tahoe.storage_broker", umid="AlxzqA",
|
||||||
level=log.UNUSUAL)
|
level=log.UNUSUAL)
|
||||||
return
|
return
|
||||||
s = NativeStorageServer(server_id, ann, self._tub_maker, {})
|
s = NativeStorageServer(server_id, ann, self._tub_maker, {}, self._grid_manager_keys, self._node_pubkey)
|
||||||
s.on_status_changed(lambda _: self._got_connection())
|
s.on_status_changed(lambda _: self._got_connection())
|
||||||
server_id = s.get_serverid()
|
server_id = s.get_serverid()
|
||||||
old = self.servers.get(server_id)
|
old = self.servers.get(server_id)
|
||||||
@ -192,11 +198,26 @@ class StorageFarmBroker(service.MultiService):
|
|||||||
for dsc in self.servers.values():
|
for dsc in self.servers.values():
|
||||||
dsc.try_to_connect()
|
dsc.try_to_connect()
|
||||||
|
|
||||||
def get_servers_for_psi(self, peer_selection_index):
|
def get_servers_for_psi(self, peer_selection_index, for_upload=False):
|
||||||
|
"""
|
||||||
|
:param for_upload: used to determine if we should include any
|
||||||
|
servers that are invalid according to Grid Manager
|
||||||
|
processing. When for_upload is True and we have any Grid
|
||||||
|
Manager keys configured, any storage servers with invalid or
|
||||||
|
missing certificates will be excluded.
|
||||||
|
"""
|
||||||
# return a list of server objects (IServers)
|
# return a list of server objects (IServers)
|
||||||
assert self.permute_peers == True
|
assert self.permute_peers == True
|
||||||
connected_servers = self.get_connected_servers()
|
connected_servers = self.get_connected_servers()
|
||||||
preferred_servers = frozenset(s for s in connected_servers if s.get_longname() in self.preferred_peers)
|
preferred_servers = frozenset(s for s in connected_servers if s.get_longname() in self.preferred_peers)
|
||||||
|
if for_upload:
|
||||||
|
print("upload processing: {}".format([srv.upload_permitted() for srv in connected_servers]))
|
||||||
|
connected_servers = [
|
||||||
|
srv
|
||||||
|
for srv in connected_servers
|
||||||
|
if srv.upload_permitted()
|
||||||
|
]
|
||||||
|
|
||||||
def _permuted(server):
|
def _permuted(server):
|
||||||
seed = server.get_permutation_seed()
|
seed = server.get_permutation_seed()
|
||||||
is_unpreferred = server not in preferred_servers
|
is_unpreferred = server not in preferred_servers
|
||||||
@ -248,6 +269,38 @@ class StubServer(object):
|
|||||||
def get_nickname(self):
|
def get_nickname(self):
|
||||||
return "?"
|
return "?"
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_grid_manager_certificate(gm_key, alleged_cert, storage_pubkey):
|
||||||
|
"""
|
||||||
|
:param gm_key: a VerifyingKey instance, a Grid Manager's public
|
||||||
|
key.
|
||||||
|
|
||||||
|
:param cert: dict with "certificate" and "signature" keys, where
|
||||||
|
"certificate" contains a JSON-serialized certificate for a Storage
|
||||||
|
Server (comes from a Grid Manager).
|
||||||
|
|
||||||
|
:return: False if the signature is invalid or the certificate is
|
||||||
|
expired.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
gm_key.verify(
|
||||||
|
alleged_cert['signature'],
|
||||||
|
alleged_cert['certificate'],
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
# signature is valid; now we can load the actual data
|
||||||
|
cert = json.loads(alleged_cert['certificate'])
|
||||||
|
now = datetime.utcnow()
|
||||||
|
expires = datetime.fromordinal(cert['expires'])
|
||||||
|
cert_pubkey = keyutil.parse_pubkey(cert['public_key'])
|
||||||
|
if cert_pubkey != storage_pubkey:
|
||||||
|
return False # certificate is for wrong server
|
||||||
|
if expires < now:
|
||||||
|
return False # certificate is expired
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@implementer(IServer)
|
@implementer(IServer)
|
||||||
class NativeStorageServer(service.MultiService):
|
class NativeStorageServer(service.MultiService):
|
||||||
"""I hold information about a storage server that we want to connect to.
|
"""I hold information about a storage server that we want to connect to.
|
||||||
@ -276,13 +329,15 @@ class NativeStorageServer(service.MultiService):
|
|||||||
"application-version": "unknown: no get_version()",
|
"application-version": "unknown: no get_version()",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, server_id, ann, tub_maker, handler_overrides):
|
def __init__(self, server_id, ann, tub_maker, handler_overrides, grid_manager_keys, node_pubkey):
|
||||||
service.MultiService.__init__(self)
|
service.MultiService.__init__(self)
|
||||||
assert isinstance(server_id, str)
|
assert isinstance(server_id, str)
|
||||||
self._server_id = server_id
|
self._server_id = server_id
|
||||||
self.announcement = ann
|
self.announcement = ann
|
||||||
self._tub_maker = tub_maker
|
self._tub_maker = tub_maker
|
||||||
self._handler_overrides = handler_overrides
|
self._handler_overrides = handler_overrides
|
||||||
|
self._node_pubkey = node_pubkey
|
||||||
|
self._grid_manager_keys = grid_manager_keys
|
||||||
|
|
||||||
assert "anonymous-storage-FURL" in ann, ann
|
assert "anonymous-storage-FURL" in ann, ann
|
||||||
furl = str(ann["anonymous-storage-FURL"])
|
furl = str(ann["anonymous-storage-FURL"])
|
||||||
@ -321,6 +376,33 @@ class NativeStorageServer(service.MultiService):
|
|||||||
self._trigger_cb = None
|
self._trigger_cb = None
|
||||||
self._on_status_changed = ObserverList()
|
self._on_status_changed = ObserverList()
|
||||||
|
|
||||||
|
def upload_permitted(self):
|
||||||
|
"""
|
||||||
|
If our client is configured with Grid Manager public-keys, we will
|
||||||
|
only upload to storage servers that have a currently-valid
|
||||||
|
certificate signed by at least one of the Grid Managers we
|
||||||
|
accept.
|
||||||
|
|
||||||
|
:return: True if we should use this server for uploads, False
|
||||||
|
otherwise.
|
||||||
|
"""
|
||||||
|
# if we have no Grid Manager keys configured, choice is easy
|
||||||
|
if not self._grid_manager_keys:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# XXX probably want to cache the answer to this? (ignoring
|
||||||
|
# that for now because certificates expire, so .. slightly
|
||||||
|
# more complex)
|
||||||
|
certificates = self.announcements.get("grid-manager-certificates", None)
|
||||||
|
if not certificates:
|
||||||
|
return False
|
||||||
|
for gm_key in self._grid_manager_keys:
|
||||||
|
for cert in certificates:
|
||||||
|
if _validate_grid_manager_certificate(gm_key, cert, self._pubkey):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def on_status_changed(self, status_changed):
|
def on_status_changed(self, status_changed):
|
||||||
"""
|
"""
|
||||||
:param status_changed: a callable taking a single arg (the
|
:param status_changed: a callable taking a single arg (the
|
||||||
|
Loading…
Reference in New Issue
Block a user