source: trunk/src/allmydata/scripts/admin.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 8.3 KB
Line 
1"""
2Ported to Python 3.
3"""
4
5from six import ensure_binary
6
7from twisted.python import usage
8from twisted.python.filepath import (
9    FilePath,
10)
11from allmydata.scripts.common import (
12    BaseOptions,
13    BasedirOptions,
14)
15from allmydata.storage import (
16    crawler,
17    expirer,
18)
19from allmydata.scripts.types_ import SubCommands
20from allmydata.client import read_config
21from allmydata.grid_manager import (
22    parse_grid_manager_certificate,
23)
24from allmydata.scripts.cli import _default_nodedir
25from allmydata.util.encodingutil import argv_to_abspath
26from allmydata.util import jsonbytes
27
28class GenerateKeypairOptions(BaseOptions):
29
30    def getUsage(self, width=None):
31        t = BaseOptions.getUsage(self, width)
32        t += """
33Generate a public/private keypair, dumped to stdout as two lines of ASCII..
34
35"""
36        return t
37
38def print_keypair(options):
39    from allmydata.crypto import ed25519
40    out = options.stdout
41    private_key, public_key = ed25519.create_signing_keypair()
42    print("private:", str(ed25519.string_from_signing_key(private_key), "ascii"),
43          file=out)
44    print("public:", str(ed25519.string_from_verifying_key(public_key), "ascii"),
45          file=out)
46
47class DerivePubkeyOptions(BaseOptions):
48    def parseArgs(self, privkey):
49        self.privkey = privkey
50
51    def getSynopsis(self):
52        return "Usage: tahoe [global-options] admin derive-pubkey PRIVKEY"
53
54    def getUsage(self, width=None):
55        t = BaseOptions.getUsage(self, width)
56        t += """
57Given a private (signing) key that was previously generated with
58generate-keypair, derive the public key and print it to stdout.
59
60"""
61        return t
62
63def derive_pubkey(options):
64    out = options.stdout
65    from allmydata.crypto import ed25519
66    privkey_vs = options.privkey
67    privkey_vs = ensure_binary(privkey_vs)
68    private_key, public_key = ed25519.signing_keypair_from_string(privkey_vs)
69    print("private:", str(ed25519.string_from_signing_key(private_key), "ascii"), file=out)
70    print("public:", str(ed25519.string_from_verifying_key(public_key), "ascii"), file=out)
71    return 0
72
73
74class MigrateCrawlerOptions(BasedirOptions):
75
76    def getSynopsis(self):
77        return "Usage: tahoe [global-options] admin migrate-crawler"
78
79    def getUsage(self, width=None):
80        t = BasedirOptions.getUsage(self, width)
81        t += (
82            "The crawler data is now stored as JSON to avoid"
83            " potential security issues with pickle files.\n\nIf"
84            " you are confident the state files in the 'storage/'"
85            " subdirectory of your node are trustworthy, run this"
86            " command to upgrade them to JSON.\n\nThe files are:"
87            " lease_checker.history, lease_checker.state, and"
88            " bucket_counter.state"
89        )
90        return t
91
92
93class AddGridManagerCertOptions(BaseOptions):
94    """
95    Options for add-grid-manager-cert
96    """
97
98    optParameters = [
99        ['filename', 'f', None, "Filename of the certificate ('-', a dash, for stdin)"],
100        ['name', 'n', None, "Name to give this certificate"],
101    ]
102
103    def getSynopsis(self):
104        return "Usage: tahoe [global-options] admin add-grid-manager-cert [options]"
105
106    def postOptions(self) -> None:
107        assert self.parent is not None
108        assert self.parent.parent is not None
109
110        if self['name'] is None:
111            raise usage.UsageError(
112                "Must provide --name option"
113            )
114        if self['filename'] is None:
115            raise usage.UsageError(
116                "Must provide --filename option"
117            )
118
119        data: str
120        if self['filename'] == '-':
121            print("reading certificate from stdin", file=self.parent.parent.stderr)  # type: ignore[attr-defined]
122            data = self.parent.parent.stdin.read()  # type: ignore[attr-defined]
123            if len(data) == 0:
124                raise usage.UsageError(
125                    "Reading certificate from stdin failed"
126                )
127        else:
128            with open(self['filename'], 'r') as f:
129                data = f.read()
130
131        try:
132            self.certificate_data = parse_grid_manager_certificate(data)
133        except ValueError as e:
134            raise usage.UsageError(
135                "Error parsing certificate: {}".format(e)
136            )
137
138    def getUsage(self, width=None):
139        t = BaseOptions.getUsage(self, width)
140        t += (
141            "Adds a Grid Manager certificate to a Storage Server.\n\n"
142            "The certificate will be copied into the base-dir and config\n"
143            "will be added to 'tahoe.cfg', which will be re-written. A\n"
144            "restart is required for changes to take effect.\n\n"
145            "The human who operates a Grid Manager would produce such a\n"
146            "certificate and communicate it securely to you.\n"
147        )
148        return t
149
150
151def migrate_crawler(options):
152    out = options.stdout
153    storage = FilePath(options['basedir']).child("storage")
154
155    conversions = [
156        (storage.child("lease_checker.state"), crawler._convert_pickle_state_to_json),
157        (storage.child("bucket_counter.state"), crawler._convert_pickle_state_to_json),
158        (storage.child("lease_checker.history"), expirer._convert_pickle_state_to_json),
159    ]
160
161    for fp, converter in conversions:
162        existed = fp.exists()
163        newfp = crawler._upgrade_pickle_to_json(fp, converter)
164        if existed:
165            print("Converted '{}' to '{}'".format(fp.path, newfp.path), file=out)
166        else:
167            if newfp.exists():
168                print("Already converted: '{}'".format(newfp.path), file=out)
169            else:
170                print("Not found: '{}'".format(fp.path), file=out)
171
172
173def add_grid_manager_cert(options):
174    """
175    Add a new Grid Manager certificate to our config
176    """
177    # XXX is there really not already a function for this?
178    if options.parent.parent['node-directory']:
179        nd = argv_to_abspath(options.parent.parent['node-directory'])
180    else:
181        nd = _default_nodedir
182
183    config = read_config(nd, "portnum")
184    cert_fname = "{}.cert".format(options['name'])
185    cert_path = FilePath(config.get_config_path(cert_fname))
186    cert_bytes = jsonbytes.dumps_bytes(options.certificate_data, indent=4) + b'\n'
187    cert_name = options['name']
188
189    if cert_path.exists():
190        msg = "Already have certificate for '{}' (at {})".format(
191            options['name'],
192            cert_path.path,
193        )
194        print(msg, file=options.stderr)
195        return 1
196
197    config.set_config("storage", "grid_management", "True")
198    config.set_config("grid_manager_certificates", cert_name, cert_fname)
199
200    # write all the data out
201    with cert_path.open("wb") as f:
202        f.write(cert_bytes)
203
204    cert_count = len(config.enumerate_section("grid_manager_certificates"))
205    print("There are now {} certificates".format(cert_count),
206          file=options.stderr)
207
208    return 0
209
210
211class AdminCommand(BaseOptions):
212    subCommands = [
213        ("generate-keypair", None, GenerateKeypairOptions,
214         "Generate a public/private keypair, write to stdout."),
215        ("derive-pubkey", None, DerivePubkeyOptions,
216         "Derive a public key from a private key."),
217        ("migrate-crawler", None, MigrateCrawlerOptions,
218         "Write the crawler-history data as JSON."),
219        ("add-grid-manager-cert", None, AddGridManagerCertOptions,
220         "Add a Grid Manager-provided certificate to a storage "
221         "server's config."),
222        ]
223    def postOptions(self):
224        if not hasattr(self, 'subOptions'):
225            raise usage.UsageError("must specify a subcommand")
226    def getSynopsis(self):
227        return "Usage: tahoe [global-options] admin SUBCOMMAND"
228    def getUsage(self, width=None):
229        t = BaseOptions.getUsage(self, width)
230        t += """
231Please run e.g. 'tahoe admin generate-keypair --help' for more details on
232each subcommand.
233"""
234        return t
235
236
237subDispatch = {
238    "generate-keypair": print_keypair,
239    "derive-pubkey": derive_pubkey,
240    "migrate-crawler": migrate_crawler,
241    "add-grid-manager-cert": add_grid_manager_cert,
242}
243
244
245def do_admin(options):
246    so = options.subOptions
247    so.stdout = options.stdout
248    so.stderr = options.stderr
249    f = subDispatch[options.subCommand]
250    return f(so)
251
252
253subCommands : SubCommands = [
254    ("admin", None, AdminCommand, "admin subcommands: use 'tahoe admin' for a list"),
255    ]
256
257dispatch = {
258    "admin": do_admin,
259}
Note: See TracBrowser for help on using the repository browser.