'tahoe admin add-grid-manager-cert' command

This commit is contained in:
meejah 2018-12-17 22:38:06 -07:00
parent 08937c6acf
commit d302a98672
2 changed files with 130 additions and 2 deletions

View File

@ -1,7 +1,17 @@
from __future__ import print_function from __future__ import print_function
import sys
import json
from os.path import exists, join
from twisted.python import usage from twisted.python import usage
#from allmydata.node import read_config
from allmydata.client import read_config
from allmydata.scripts.cli import _default_nodedir
from allmydata.scripts.common import BaseOptions from allmydata.scripts.common import BaseOptions
from allmydata.util.encodingutil import argv_to_abspath
from allmydata.util import fileutil
class GenerateKeypairOptions(BaseOptions): class GenerateKeypairOptions(BaseOptions):
@ -46,10 +56,93 @@ def derive_pubkey(options):
return 0 return 0
class AddGridManagerCertOptions(BaseOptions):
optParameters = [
['filename', 'f', None, "Filename of the certificate ('-', a dash, for stdin)"],
['name', 'n', "default", "Name to give this certificate"],
]
def getSynopsis(self):
return "Usage: tahoe [global-options] admin add-grid-manager-cert [options]"
def postOptions(self):
if self['filename'] is None:
raise usage.UsageError(
"Must provide --filename option"
)
if self['filename'] == '-':
print >>self.parent.parent.stderr, "reading certificate from stdin"
data = sys.stdin.read()
if len(data) == 0:
raise usage.UsageError(
"Reading certificate from stdin failed"
)
from allmydata.storage_client import parse_grid_manager_data
try:
self.certificate_data = parse_grid_manager_data(data)
except ValueError as e:
print >>self.parent.parent.stderr, "Error parsing certificate: {}".format(e)
self.certificate_data = None
else:
with open(self['filename'], 'r') as f:
self.certificate_data = f.read()
def getUsage(self, width=None):
t = BaseOptions.getUsage(self, width)
t += (
"Adds a Grid Manager certificate to a Storage Server.\n\n"
"The certificate will be copied into the base-dir and config\n"
"will be added to 'tahoe.cfg', which will be re-written. A\n"
"restart is required for changes to take effect.\n\n"
"The human who operates a Grid Manager would produce such a\n"
"certificate and communicate it securely to you.\n"
)
return t
def add_grid_manager_cert(options): def add_grid_manager_cert(options):
""" """
Add a new Grid Manager certificate to our config Add a new Grid Manager certificate to our config
""" """
if options.certificate_data is None:
return 1
# XXX is there really not already a function for this?
if options.parent.parent['node-directory']:
nd = argv_to_abspath(options.parent.parent['node-directory'])
else:
nd = _default_nodedir
config = read_config(nd, "portnum")
config_path = join(nd, "tahoe.cfg")
cert_fname = "{}.cert".format(options['name'])
cert_path = config.get_config_path(cert_fname)
cert_bytes = json.dumps(options.certificate_data, indent=4) + '\n'
cert_name = options['name']
if exists(cert_path):
print >>options.parent.parent.stderr, "Already have file '{}'".format(cert_path)
return 1
cfg = config.config # why aren't methods we call on cfg in _Config itself?
gm_certs = config.get_config("storage", "grid_manager_certificate_files", "").split()
if cert_fname not in gm_certs:
gm_certs.append(cert_fname)
cfg.set("storage", "grid_manager_certificate_files", " ".join(gm_certs))
# print("grid_manager_certificate_files in {}: {}".format(config_path, len(gm_certs)))
# write all the data out
fileutil.write(cert_path, cert_bytes)
# print("created {}: {} bytes".format(cert_fname, len(cert_bytes)))
with open(config_path, "w") as f:
cfg.write(f)
# print("wrote {}".format(config_fname))
print >>options.parent.parent.stderr, "There are now {} certificates".format(len(gm_certs))
return 0 return 0
@ -59,6 +152,9 @@ class AdminCommand(BaseOptions):
"Generate a public/private keypair, write to stdout."), "Generate a public/private keypair, write to stdout."),
("derive-pubkey", None, DerivePubkeyOptions, ("derive-pubkey", None, DerivePubkeyOptions,
"Derive a public key from a private key."), "Derive a public key from a private key."),
("add-grid-manager-cert", None, AddGridManagerCertOptions,
"Add a Grid Manager-provided certificate to a storage "
"server's config."),
] ]
def postOptions(self): def postOptions(self):
if not hasattr(self, 'subOptions'): if not hasattr(self, 'subOptions'):

View File

@ -282,7 +282,39 @@ class StubServer(object):
return "?" return "?"
def _validate_grid_manager_certificate(gm_key, alleged_cert): def parse_grid_manager_data(gm_data):
"""
:param gm_data: some data that might be JSON that might be a valid
Grid Manager Certificate
:returns: json data of a valid Grid Manager certificate, or an
exception if the data is not valid.
"""
required_keys = allowed_keys = [
'certificate',
'signature',
]
js = json.loads(gm_data)
for k in js.keys():
if k not in allowed_keys:
raise ValueError(
"Grid Manager certificate JSON may not contain '{}'".format(
k,
)
)
for k in allowed_keys:
if k not in js:
raise ValueError(
"Grid Manager certificate JSON must contain '{}'".format(
k,
)
)
return js
def validate_grid_manager_certificate(gm_key, alleged_cert):
""" """
:param gm_key: a VerifyingKey instance, a Grid Manager's public :param gm_key: a VerifyingKey instance, a Grid Manager's public
key. key.
@ -423,7 +455,7 @@ class NativeStorageServer(service.MultiService):
return False return False
for gm_key in self._grid_manager_keys: for gm_key in self._grid_manager_keys:
for cert in self._grid_manager_certificates: for cert in self._grid_manager_certificates:
if _validate_grid_manager_certificate(gm_key, cert): if validate_grid_manager_certificate(gm_key, cert):
# print("valid: {}\n{}".format(gm_key, cert)) # print("valid: {}\n{}".format(gm_key, cert))
return True return True
# print("didn't validate {} keys".format(len(self._grid_manager_keys))) # print("didn't validate {} keys".format(len(self._grid_manager_keys)))