refactor (review): move grid-manager certificate loading code out of cli
This commit is contained in:
parent
649cb9380e
commit
938cc5616e
|
@ -152,23 +152,16 @@ def list(ctx):
|
||||||
"""
|
"""
|
||||||
List all storage-servers known to a Grid Manager
|
List all storage-servers known to a Grid Manager
|
||||||
"""
|
"""
|
||||||
fp = _config_path_from_option(ctx.parent.params["config"])
|
|
||||||
for name in sorted(ctx.obj.grid_manager.storage_servers.keys()):
|
for name in sorted(ctx.obj.grid_manager.storage_servers.keys()):
|
||||||
blank_name = " " * len(name)
|
blank_name = " " * len(name)
|
||||||
click.echo("{}: {}".format(name, ctx.obj.grid_manager.storage_servers[name].public_key()))
|
click.echo("{}: {}".format(name, ctx.obj.grid_manager.storage_servers[name].public_key_string()))
|
||||||
if fp:
|
for cert in ctx.obj.grid_manager.storage_servers[name].certificates:
|
||||||
cert_count = 0
|
delta = datetime.utcnow() - cert.expires
|
||||||
while fp.child('{}.cert.{}'.format(name, cert_count)).exists():
|
click.echo("{} cert {}: ".format(blank_name, cert.index), nl=False)
|
||||||
container = json.load(fp.child('{}.cert.{}'.format(name, cert_count)).open('r'))
|
if delta.total_seconds() < 0:
|
||||||
cert_data = json.loads(container['certificate'])
|
click.echo("valid until {} ({})".format(cert.expires, abbreviate_time(delta)))
|
||||||
expires = datetime.utcfromtimestamp(cert_data['expires'])
|
else:
|
||||||
delta = datetime.utcnow() - expires
|
click.echo("expired {} ({})".format(cert.expires, abbreviate_time(delta)))
|
||||||
click.echo("{} cert {}: ".format(blank_name, cert_count), nl=False)
|
|
||||||
if delta.total_seconds() < 0:
|
|
||||||
click.echo("valid until {} ({})".format(expires, abbreviate_time(delta)))
|
|
||||||
else:
|
|
||||||
click.echo("expired {} ({})".format(expires, abbreviate_time(delta)))
|
|
||||||
cert_count += 1
|
|
||||||
|
|
||||||
|
|
||||||
@grid_manager.command()
|
@grid_manager.command()
|
||||||
|
@ -196,15 +189,20 @@ def sign(ctx, name, expiry_days):
|
||||||
click.echo(certificate_data)
|
click.echo(certificate_data)
|
||||||
if fp is not None:
|
if fp is not None:
|
||||||
next_serial = 0
|
next_serial = 0
|
||||||
while fp.child("{}.cert.{}".format(name, next_serial)).exists():
|
f = None
|
||||||
|
while f is None:
|
||||||
|
try:
|
||||||
|
f = fp.child("{}.cert.{}".format(name, next_serial)).create()
|
||||||
|
except Exception:
|
||||||
|
f = None
|
||||||
next_serial += 1
|
next_serial += 1
|
||||||
with fp.child('{}.cert.{}'.format(name, next_serial)).open('w') as f:
|
with f:
|
||||||
f.write(certificate_data)
|
f.write(certificate_data)
|
||||||
|
|
||||||
|
|
||||||
def _config_path_from_option(config):
|
def _config_path_from_option(config):
|
||||||
"""
|
"""
|
||||||
:param string config: a path or -
|
:param str config: a path or -
|
||||||
:returns: a FilePath instance or None
|
:returns: a FilePath instance or None
|
||||||
"""
|
"""
|
||||||
if config == "-":
|
if config == "-":
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
from datetime import (
|
from datetime import (
|
||||||
|
@ -13,29 +14,46 @@ from allmydata.util import (
|
||||||
base32,
|
base32,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class _GridManagerStorageServer(object):
|
class _GridManagerStorageServer(object):
|
||||||
"""
|
"""
|
||||||
A Grid Manager's notion of a storage server
|
A Grid Manager's notion of a storage server
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, public_key, certificates):
|
name = attr.ib()
|
||||||
self.name = name
|
public_key = attr.ib(validator=attr.validators.instance_of(ed25519.Ed25519PublicKey))
|
||||||
self._public_key = public_key
|
certificates = attr.ib(
|
||||||
self._certificates = [] if certificates is None else certificates
|
default=attr.Factory(list),
|
||||||
|
validator=attr.validators.instance_of(list),
|
||||||
|
)
|
||||||
|
|
||||||
def add_certificate(self, certificate):
|
def add_certificate(self, certificate):
|
||||||
self._certificates.append(certificate)
|
self.certificates.append(certificate)
|
||||||
|
|
||||||
def public_key(self):
|
def public_key_string(self):
|
||||||
return ed25519.string_from_verifying_key(self._public_key)
|
return ed25519.string_from_verifying_key(self.public_key)
|
||||||
|
|
||||||
def marshal(self):
|
def marshal(self):
|
||||||
return {
|
return {
|
||||||
u"public_key": self.public_key(),
|
u"public_key": self.public_key_string(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class _GridManagerCertificate(object):
|
||||||
|
"""
|
||||||
|
Represents a single certificate for a single storage-server
|
||||||
|
"""
|
||||||
|
|
||||||
|
filename = attr.ib()
|
||||||
|
index = attr.ib(validator=attr.validators.instance_of(int))
|
||||||
|
expires = attr.ib(validator=attr.validators.instance_of(datetime))
|
||||||
|
public_key = attr.ib(validator=attr.validators.instance_of(ed25519.Ed25519PublicKey))
|
||||||
|
|
||||||
|
|
||||||
def create_grid_manager():
|
def create_grid_manager():
|
||||||
"""
|
"""
|
||||||
Create a new Grid Manager with a fresh keypair
|
Create a new Grid Manager with a fresh keypair
|
||||||
|
@ -47,6 +65,52 @@ def create_grid_manager():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_certificates_for(config_path, name, gm_key=None):
|
||||||
|
"""
|
||||||
|
Load any existing certificates for the given storage-server.
|
||||||
|
|
||||||
|
:param FilePath config_path: the configuration location (or None for
|
||||||
|
stdin)
|
||||||
|
|
||||||
|
:param str name: the name of an existing storage-server
|
||||||
|
|
||||||
|
:param ed25519.VerifyingKey gm_key: an optional Grid Manager
|
||||||
|
public key. If provided, certificates will be verified against it.
|
||||||
|
|
||||||
|
:returns: list containing any known certificates (may be empty)
|
||||||
|
|
||||||
|
:raises: ed25519.BadSignature if any certificate signature fails to verify
|
||||||
|
"""
|
||||||
|
if config_path is None:
|
||||||
|
return []
|
||||||
|
cert_index = 0
|
||||||
|
cert_path = config_path.child('{}.cert.{}'.format(name, cert_index))
|
||||||
|
certificates = []
|
||||||
|
while cert_path.exists():
|
||||||
|
container = json.load(cert_path.open('r'))
|
||||||
|
if gm_key is not None:
|
||||||
|
validate_grid_manager_certificate(gm_key, container)
|
||||||
|
cert_data = json.loads(container['certificate'])
|
||||||
|
if cert_data['version'] != 1:
|
||||||
|
raise ValueError(
|
||||||
|
"Unknown certificate version '{}' in '{}'".format(
|
||||||
|
cert_data['version'],
|
||||||
|
cert_path.path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
certificates.append(
|
||||||
|
_GridManagerCertificate(
|
||||||
|
filename=cert_path.path,
|
||||||
|
index=cert_index,
|
||||||
|
expires=datetime.utcfromtimestamp(cert_data['expires']),
|
||||||
|
public_key=ed25519.verifying_key_from_string(cert_data['public_key'].encode('ascii')),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cert_index += 1
|
||||||
|
cert_path = config_path.child('{}.cert.{}'.format(name, cert_index))
|
||||||
|
return certificates
|
||||||
|
|
||||||
|
|
||||||
def load_grid_manager(config_path):
|
def load_grid_manager(config_path):
|
||||||
"""
|
"""
|
||||||
Load a Grid Manager from existing configuration.
|
Load a Grid Manager from existing configuration.
|
||||||
|
@ -56,17 +120,15 @@ def load_grid_manager(config_path):
|
||||||
|
|
||||||
:returns: a GridManager instance
|
:returns: a GridManager instance
|
||||||
|
|
||||||
:raises: ValueError if the confguration is invalid
|
:raises: ValueError if the confguration is invalid or IOError if
|
||||||
|
expected files can't be opened.
|
||||||
"""
|
"""
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
config_file = sys.stdin
|
config_file = sys.stdin
|
||||||
else:
|
else:
|
||||||
try:
|
# this might raise IOError or similar but caller must handle it
|
||||||
config_file = config_path.child("config.json").open("r")
|
config_file = config_path.child("config.json").open("r")
|
||||||
except IOError:
|
|
||||||
raise ValueError(
|
|
||||||
"'{}' is not a Grid Manager config-directory".format(config_path)
|
|
||||||
)
|
|
||||||
with config_file:
|
with config_file:
|
||||||
config = json.load(config_file)
|
config = json.load(config_file)
|
||||||
|
|
||||||
|
@ -99,7 +161,7 @@ def load_grid_manager(config_path):
|
||||||
storage_servers[name] = _GridManagerStorageServer(
|
storage_servers[name] = _GridManagerStorageServer(
|
||||||
name,
|
name,
|
||||||
ed25519.verifying_key_from_string(srv_config['public_key'].encode('ascii')),
|
ed25519.verifying_key_from_string(srv_config['public_key'].encode('ascii')),
|
||||||
None,
|
_load_certificates_for(config_path, name, public_key),
|
||||||
)
|
)
|
||||||
|
|
||||||
return _GridManager(private_key_bytes, storage_servers)
|
return _GridManager(private_key_bytes, storage_servers)
|
||||||
|
@ -134,7 +196,7 @@ class _GridManager(object):
|
||||||
epoch_offset = (expiration - datetime(1970, 1, 1)).total_seconds()
|
epoch_offset = (expiration - datetime(1970, 1, 1)).total_seconds()
|
||||||
cert_info = {
|
cert_info = {
|
||||||
"expires": epoch_offset,
|
"expires": epoch_offset,
|
||||||
"public_key": srv.public_key(),
|
"public_key": srv.public_key_string(),
|
||||||
"version": 1,
|
"version": 1,
|
||||||
}
|
}
|
||||||
cert_data = json.dumps(cert_info, separators=(',',':'), sort_keys=True).encode('utf8')
|
cert_data = json.dumps(cert_info, separators=(',',':'), sort_keys=True).encode('utf8')
|
||||||
|
@ -161,7 +223,7 @@ class _GridManager(object):
|
||||||
raise KeyError(
|
raise KeyError(
|
||||||
"Already have a storage server called '{}'".format(name)
|
"Already have a storage server called '{}'".format(name)
|
||||||
)
|
)
|
||||||
ss = _GridManagerStorageServer(name, public_key, None)
|
ss = _GridManagerStorageServer(name, public_key, [])
|
||||||
self._storage_servers[name] = ss
|
self._storage_servers[name] = ss
|
||||||
return ss
|
return ss
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,7 @@ class GridManagerVerifier(SyncTestCase):
|
||||||
for name, ss0 in self.gm.storage_servers.items():
|
for name, ss0 in self.gm.storage_servers.items():
|
||||||
ss1 = gm2.storage_servers[name]
|
ss1 = gm2.storage_servers[name]
|
||||||
self.assertEqual(ss0.name, ss1.name)
|
self.assertEqual(ss0.name, ss1.name)
|
||||||
self.assertEqual(ss0.public_key(), ss1.public_key())
|
self.assertEqual(ss0.public_key_string(), ss1.public_key_string())
|
||||||
self.assertEqual(self.gm.marshal(), gm2.marshal())
|
self.assertEqual(self.gm.marshal(), gm2.marshal())
|
||||||
|
|
||||||
def test_invalid_no_version(self):
|
def test_invalid_no_version(self):
|
||||||
|
|
Loading…
Reference in New Issue