fa1083e98907a6a7dc48e8decfa7146708510341
Marco Ricci Add command-line interface...

Marco Ricci authored 3 weeks ago

1) # SPDX-FileCopyrightText: 2024 Marco Ricci <m@the13thletter.info>
2) #
3) # SPDX-License-Identifier: MIT
4) 
5) """Command-line interface for derivepassphrase_export."""
6) 
7) from __future__ import annotations
8) 
9) import base64
10) import importlib
11) import json
12) import logging
Marco Ricci Move vault key and path det...

Marco Ricci authored 1 month ago

13) import os
Marco Ricci Add command-line interface...

Marco Ricci authored 3 weeks ago

14) from typing import TYPE_CHECKING, Any, Literal
15) 
16) import click
17) from typing_extensions import assert_never
18) 
19) import derivepassphrase as dpp
20) from derivepassphrase import _types
21) 
22) if TYPE_CHECKING:
23)     import types
24)     from collections.abc import Sequence
25) 
26) __author__ = dpp.__author__
27) __version__ = dpp.__version__
28) 
29) __all__ = ('derivepassphrase_export',)
30) 
31) PROG_NAME = 'derivepassphrase_export'
Marco Ricci Move vault key and path det...

Marco Ricci authored 1 month ago

32) 
33) 
34) def get_vault_key() -> bytes:
35)     """Automatically determine the vault master key/password.
36) 
37)     Query the `VAULT_KEY`, `LOGNAME`, `USER` and `USERNAME` environment
38)     variables, in that order.  This is the same algorithm as vault uses.
39) 
40)     Returns:
41)         The master key/password.  This is generally used as input to
42)         a key-derivation function to determine the *actual* encryption
43)         and signing keys for the vault configuration.
44) 
45)     Raises:
46)         KeyError:
47)             We cannot find any of the named environment variables.
48)             Please set `VAULT_KEY` manually to the desired value.
49) 
50)     """
51) 
52)     username = (
53)         os.environb.get(b'VAULT_KEY')
54)         or os.environb.get(b'LOGNAME')
55)         or os.environb.get(b'USER')
56)         or os.environb.get(b'USERNAME')
57)     )
58)     if not username:
59)         env_var = 'VAULT_KEY'
60)         raise KeyError(env_var)
61)     return username
62) 
63) 
64) def get_vault_path() -> str | bytes | os.PathLike:
65)     """Automatically determine the vault configuration path.
66) 
67)     Query the `VAULT_PATH` environment variable, or default to
68)     `~/.vault`.  This is the same algorithm as vault uses.  If not
69)     absolute, then `VAULT_PATH` is relative to the home directory.
70) 
71)     Returns:
72)         The vault configuration path.  Depending on the vault version,
73)         this may be a file or a directory.
74) 
75)     Raises:
76)         RuntimeError:
77)             We cannot determine the home directory.  Please set `HOME`
78)             manually to the correct value.
79) 
80)     """
81) 
82)     result = os.path.join(
83)         os.path.expanduser('~'), os.environ.get('VAULT_PATH', '.vault')
84)     )
85)     if result.startswith('~'):
86)         msg = 'Cannot determine home directory'
87)         raise RuntimeError(msg)
88)     return result
Marco Ricci Add command-line interface...

Marco Ricci authored 3 weeks ago

89) 
90) 
91) @click.command(
92)     context_settings={'help_option_names': ['-h', '--help']},
93) )
94) @click.option(
95)     '-f',
96)     '--format',
97)     'formats',
98)     metavar='FMT',
99)     multiple=True,
100)     default=('v0.3', 'v0.2', 'storeroom'),
101)     type=click.Choice(['v0.2', 'v0.3', 'storeroom']),
102)     help='try the following storage formats, in order (default: v0.3, v0.2)',
103) )
104) @click.option(
105)     '-k',
106)     '--key',
107)     metavar='K',
108)     help=(
109)         'use K as the storage master key '
110)         '(default: check the `VAULT_KEY`, `LOGNAME`, `USER` or '
111)         '`USERNAME` environment variables)'
112)     ),
113) )
114) @click.argument('path', metavar='PATH', required=True)
115) @click.pass_context
116) def derivepassphrase_export(
117)     ctx: click.Context,
118)     /,
119)     *,
120)     path: str | bytes | os.PathLike[str],
121)     formats: Sequence[Literal['v0.2', 'v0.3', 'storeroom']] = (),
122)     key: str | bytes | None = None,
123) ) -> None:
124)     """Export a vault-native configuration to standard output.
125) 
126)     Read the vault-native configuration at PATH, extract all information
127)     from it, and export the resulting configuration to standard output.
128)     Depending on the configuration format, this may either be a file or
129)     a directory.
130) 
131)     If PATH is explicitly given as `VAULT_PATH`, then use the
132)     `VAULT_PATH` environment variable to determine the correct path.
133)     (Use `./VAULT_PATH` or similar to indicate a file/directory actually
134)     named `VAULT_PATH`.)
135) 
136)     """
137) 
138)     def load_data(
139)         fmt: Literal['v0.2', 'v0.3', 'storeroom'],
140)         path: str | bytes | os.PathLike[str],
141)         key: bytes,
142)     ) -> Any:
143)         contents: bytes
144)         module: types.ModuleType
145)         match fmt:
146)             case 'v0.2':
147)                 module = importlib.import_module(
148)                     'derivepassphrase.exporter.vault_v03_and_below'
149)                 )
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 3 weeks ago

150)                 if module.STUBBED:
151)                     raise ModuleNotFoundError
Marco Ricci Add command-line interface...

Marco Ricci authored 3 weeks ago

152)                 with open(path, 'rb') as infile:
153)                     contents = base64.standard_b64decode(infile.read())
154)                 return module.V02Reader(contents, key).run()
155)             case 'v0.3':
156)                 module = importlib.import_module(
157)                     'derivepassphrase.exporter.vault_v03_and_below'
158)                 )
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 3 weeks ago

159)                 if module.STUBBED:
160)                     raise ModuleNotFoundError
Marco Ricci Add command-line interface...

Marco Ricci authored 3 weeks ago

161)                 with open(path, 'rb') as infile:
162)                     contents = base64.standard_b64decode(infile.read())
163)                 return module.V03Reader(contents, key).run()
164)             case 'storeroom':
165)                 module = importlib.import_module(
166)                     'derivepassphrase.exporter.storeroom'
167)                 )
Marco Ricci Add preliminary tests for t...

Marco Ricci authored 3 weeks ago

168)                 if module.STUBBED:
169)                     raise ModuleNotFoundError