more argument parsing
This commit is contained in:
parent
bc755a8068
commit
f957ad49b9
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,3 +28,4 @@ testing/local.test.config.xml
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
logs/
|
logs/
|
||||||
docs/README.html
|
docs/README.html
|
||||||
|
docs/vaultpass.1.gz
|
||||||
|
@ -344,7 +344,6 @@ lCmbJtQcjxG/eJ/SrB2oS47YdEKRy+cH0Xx+
|
|||||||
<BINARY DATA>
|
<BINARY DATA>
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
===== Decrypted
|
===== Decrypted
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
@ -356,3 +355,92 @@ lCmbJtQcjxG/eJ/SrB2oS47YdEKRy+cH0Xx+
|
|||||||
|
|
||||||
</auth>
|
</auth>
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
|
== Known Incompatibilities with Pass
|
||||||
|
=== **`PASSWORD_STORE_ENABLE_EXTENSIONS`**,`.extensions/COMMAND.bash`, and Default Subcommands
|
||||||
|
==== Issue Description
|
||||||
|
Per the Pass man page:
|
||||||
|
|
||||||
|
.PASS(1)
|
||||||
|
....
|
||||||
|
If no COMMAND is specified, COMMAND defaults to either show or ls, depending on the type of specifier in ARGS. Alternatively, if PASSWORD_STORE_ENABLE_EXTENSIONS is set to "true", and the file .extensions/COMMAND.bash exists inside the password store and is executable, then it is sourced into the environment, passing any arguments and environment variables. Extensions existing in a system-wide directory, only installable by the administrator, are always enabled.
|
||||||
|
....
|
||||||
|
|
||||||
|
Due to this being Python, we lose some of this compatability. It may be possible to add this functionality in the
|
||||||
|
future, but it's lower priority currently.
|
||||||
|
|
||||||
|
Similarly, we cannot set a default subcommand as of yet in Python via `argparse` (the library that VaultPass uses to
|
||||||
|
parse command-line arguments).
|
||||||
|
|
||||||
|
==== Workaround(s)
|
||||||
|
You can set an alias in your `~/.bashrc` that will:
|
||||||
|
|
||||||
|
. Execute `show` by default
|
||||||
|
. Provide a direct command for `ls` operations
|
||||||
|
. Specify default options for a command
|
||||||
|
|
||||||
|
Via the following:
|
||||||
|
|
||||||
|
.`~/.bashrc`:
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# 1
|
||||||
|
alias pass='vaultpass show'
|
||||||
|
|
||||||
|
# 2
|
||||||
|
alias lpass='vaultpass ls'
|
||||||
|
|
||||||
|
# 3
|
||||||
|
alias vaultpass='vaultpass -c ~/.config/alternate.vaultpass.xml'
|
||||||
|
----
|
||||||
|
|
||||||
|
To use the non-aliased command in Bash, you can either invoke the full path:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
/usr/local/bin/vaultpass edit path/to/secret
|
||||||
|
----
|
||||||
|
|
||||||
|
Or, alternatively, prefix with a backslash:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
\vaultpass edit path/to/secret
|
||||||
|
----
|
||||||
|
|
||||||
|
Finally, you can always use VaultPass by specifying the subcommand and disregard aliases entirely.
|
||||||
|
|
||||||
|
|
||||||
|
=== **find**/**search** Subcommand Searching
|
||||||
|
==== Issue Description
|
||||||
|
Pass used http://man7.org/linux/man-pages/man1/find.1.html[**find(1)**^] to search secret paths. Because we use Vault
|
||||||
|
and not a filesystem hierarchy, this isn't applicable. As such, the normal https://www.gnu.org/software/findutils/manual/html_mono/find.html[`find`^] globbing language is not supported...
|
||||||
|
|
||||||
|
==== Workaround(s)
|
||||||
|
What *is* supported, however, is regular expressions' ("regex") match patterns.
|
||||||
|
|
||||||
|
If you haven't used regexes before, here are some helpful starters/tools:
|
||||||
|
|
||||||
|
* https://www.regular-expressions.info/tutorial.html
|
||||||
|
* https://regexone.com/
|
||||||
|
* https://regexr.com/
|
||||||
|
* https://docs.python.org/library/re.html#regular-expression-syntax
|
||||||
|
* https://regexcrossword.com/
|
||||||
|
* https://learncodethehardway.org/regex/
|
||||||
|
|
||||||
|
Regular expressions are MUCH more powerful than the `find` globbing language, but do have a slight learning curve. You
|
||||||
|
will be thankful to learn their syntax, however, as they are very widely applicable.
|
||||||
|
|
||||||
|
=== Environment Variables
|
||||||
|
==== Issue Description
|
||||||
|
Pass (and to a slightly lesser extent, Vault) relies almost entirely/exclusively upon environment variables for
|
||||||
|
configuration. VaultPass does not.
|
||||||
|
|
||||||
|
==== Workaround(s)
|
||||||
|
Relying entirely on environment variables for configuration is dumb, so I don't rely on that. All persistent
|
||||||
|
configuration can be either specified in the <<configuration,configuration file>> or can be overridden by
|
||||||
|
flags/switches to subcommands. **Some** configuration directives/behaviour may be overridden by environment variables,
|
||||||
|
but by and large this is not the case.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
asciidoctor README.adoc -o ./README.html
|
asciidoctor README.adoc -o ./README.html
|
||||||
|
asciidoctor -b manpage -o - vaultpass.1.adoc | gzip -c -9 > vaultpass.1.gz
|
||||||
|
17
pass.py
Executable file
17
pass.py
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import vaultpass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
rawargs = vaultpass.args.parseArgs()
|
||||||
|
args = rawargs.parse_args()
|
||||||
|
if not args.oper:
|
||||||
|
args.oper = 'show'
|
||||||
|
import pprint
|
||||||
|
pprint.pprint(vars(args))
|
||||||
|
return(None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -1,21 +1,119 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
##
|
||||||
|
from . import constants
|
||||||
_opers = ['cp', 'edit', 'find', 'generate', 'git', 'grep', 'help', 'init', 'insert', 'ls', 'mv', 'rm', 'show',
|
|
||||||
'version', 'import'] # "import" is new
|
|
||||||
|
|
||||||
|
|
||||||
def parseArgs():
|
def parseArgs():
|
||||||
args = argparse.ArgumentParser(description = 'VaultPass - a Vault-backed Pass replacement',
|
args = argparse.ArgumentParser(description = 'VaultPass - a Vault-backed Pass replacement',
|
||||||
prog = 'pass',
|
prog = 'pass',
|
||||||
epilog = ('This program has context-specific help. Try '))
|
epilog = ('This program has context-specific help. Try "... cp --help". '
|
||||||
commonargs = argparse.ArgumentParser(add_help = False)
|
'This help output is intentionally terse; see "man 1 vaultpass" and the '
|
||||||
commonargs.add_argument('-c', '--config',
|
'README for more complete information, configuration, and usage.'))
|
||||||
default = '~/.config/vaultpass.xml',
|
args.add_argument('-c', '--config',
|
||||||
help = ('The path to your configuration file. Default: ~/.config/vaultpass.xml'))
|
default = '~/.config/vaultpass.xml',
|
||||||
|
help = ('The path to your configuration file. Default: ~/.config/vaultpass.xml'))
|
||||||
args.add_argument('oper',
|
args.add_argument('-m', '--mount',
|
||||||
choices = _opers,
|
dest = 'mount',
|
||||||
help = ('The operation to perform. Use the help operation or see the man page for more '
|
required = False,
|
||||||
'information'))
|
help = ('The mount to use in OPERATION. If not specified, assume all mounts we have access '
|
||||||
args.add_argument()
|
'to/all mounts specified in -c/--config'))
|
||||||
|
# I wish argparse supported default subcommands. It doesn't as of python 3.8.
|
||||||
|
subparser = args.add_subparsers(help = ('Operation to perform'),
|
||||||
|
metavar = 'OPERATION',
|
||||||
|
dest = 'oper')
|
||||||
|
cp = subparser.add_parser('cp',
|
||||||
|
aliases = ['copy'])
|
||||||
|
edit = subparser.add_parser('edit')
|
||||||
|
find = subparser.add_parser('find',
|
||||||
|
aliases = ['search'])
|
||||||
|
gen = subparser.add_parser('generate')
|
||||||
|
git = subparser.add_parser('git') # Dummy opt; do nothing
|
||||||
|
grep = subparser.add_parser('grep')
|
||||||
|
helpme = subparser.add_parser('help')
|
||||||
|
initvault = subparser.add_parser('init')
|
||||||
|
insertval = subparser.add_parser('insert',
|
||||||
|
aliases = ['add'])
|
||||||
|
ls = subparser.add_parser('ls',
|
||||||
|
aliases = ['list'])
|
||||||
|
mv = subparser.add_parser('mv',
|
||||||
|
aliases = ['rename'])
|
||||||
|
rm = subparser.add_parser('rm',
|
||||||
|
aliases = ['remove', 'delete'])
|
||||||
|
show = subparser.add_parser('show')
|
||||||
|
version = subparser.add_parser('version')
|
||||||
|
importvault = subparser.add_parser('import')
|
||||||
|
# CP/COPY
|
||||||
|
cp.add_argument('-f', '--force',
|
||||||
|
dest = 'force',
|
||||||
|
action = 'store_true',
|
||||||
|
help = ('If specified, replace NEWPATH if it exists'))
|
||||||
|
cp.add_argument('oldpath',
|
||||||
|
metavar = 'OLDPATH',
|
||||||
|
help = ('The original ("source") path for the secret'))
|
||||||
|
cp.add_argument('newpath',
|
||||||
|
metavar = 'NEWPATH',
|
||||||
|
help = ('The new ("destination") path for the secret'))
|
||||||
|
# EDIT
|
||||||
|
edit.add_argument('-e', '--editor',
|
||||||
|
metavar = '/PATH/TO/EDITOR',
|
||||||
|
dest = 'editor',
|
||||||
|
default = constants.EDITOR,
|
||||||
|
help = ('The editor program to use (sourced from EDITOR environment variable). '
|
||||||
|
'Default: {0}').format(constants.EDITOR))
|
||||||
|
edit.add_argument('path',
|
||||||
|
metavar = 'PATH_TO_SECRET',
|
||||||
|
help = ('Insert a new secret at PATH_TO_SECRET if it does not exist, otherwise edit it using '
|
||||||
|
'your default editor (see -e/--editor)'))
|
||||||
|
# FIND/SEARCH
|
||||||
|
find.add_argument('pattern',
|
||||||
|
metavar = 'NAME_PATTERN',
|
||||||
|
help = ('List secrets\' paths whose names match the regex NAME_PATTERN'))
|
||||||
|
# GENERATE
|
||||||
|
gen.add_argument('-n', '--no-symbols',
|
||||||
|
dest = 'symbols',
|
||||||
|
action = 'store_false',
|
||||||
|
help = ('If specified, generate a password with no non-alphanumeric chracters'))
|
||||||
|
gen.add_argument('-c', '--clip',
|
||||||
|
dest = 'clip',
|
||||||
|
action = 'store_true',
|
||||||
|
help = ('If specified, do not print the password but instead place in the clipboard for '
|
||||||
|
'a given number of seconds (see -s/--seconds)'))
|
||||||
|
gen.add_argument('-s', '--seconds',
|
||||||
|
dest = 'seconds',
|
||||||
|
type = int,
|
||||||
|
default = constants.CLIP_TIMEOUT,
|
||||||
|
help = ('If generating to the clipboard (see -c/--clip), clear the clipboard after this many '
|
||||||
|
'seconds. Default: {0}').format(constants.CLIP_TIMEOUT))
|
||||||
|
gen.add_argument('-C', '--characters',
|
||||||
|
dest = 'chars',
|
||||||
|
default = constants.SELECTED_PASS_CHARS,
|
||||||
|
help = ('The characters to use when generating a password (symbols included). '
|
||||||
|
'Default: {0}').format(constants.SELECTED_PASS_CHARS))
|
||||||
|
gen.add_argument('-Cn', '--characters-no-symbols',
|
||||||
|
dest = 'chars_plain',
|
||||||
|
default = constants.SELECTED_PASS_NOSYMBOL_CHARS,
|
||||||
|
help = ('The characters to use when generating an alphanumeric-only password, '
|
||||||
|
'Default: {0}').format(constants.SELECTED_PASS_NOSYMBOL_CHARS))
|
||||||
|
# TODO: support?
|
||||||
|
gen.add_argument('-i', '--in-place',
|
||||||
|
dest = 'in_place',
|
||||||
|
action = 'store_true',
|
||||||
|
help = ('(Unused; kept for compatibility reasons)'))
|
||||||
|
gen.add_argument('-q', '--qrcode',
|
||||||
|
dest = 'qr',
|
||||||
|
action = 'store_true',
|
||||||
|
help = ('If specified, display the password as a QR code (graphically or in-terminal depending '
|
||||||
|
'on supported environment)'))
|
||||||
|
gen.add_argument('-f', '--force',
|
||||||
|
dest = 'force',
|
||||||
|
help = ('If specified and PATH/TO/SECRET exists, overwrite without prompting first'))
|
||||||
|
gen.add_argument('path',
|
||||||
|
metavar = 'PATH/TO/SECRET',
|
||||||
|
help = ('The path to the secret'))
|
||||||
|
gen.add_argument('length',
|
||||||
|
type = int,
|
||||||
|
default = constants.GENERATED_LENGTH,
|
||||||
|
metavar = 'LENGTH',
|
||||||
|
help = ('The length (number of characters) in the generated password. '
|
||||||
|
'Default: {0}').format(constants.GENERATED_LENGTH))
|
||||||
|
return(args)
|
||||||
|
@ -1 +1,27 @@
|
|||||||
|
import os
|
||||||
|
import string
|
||||||
|
|
||||||
|
# These are static.
|
||||||
VERSION = '0.0.1'
|
VERSION = '0.0.1'
|
||||||
|
ALPHA_PASS_CHARS = string.ascii_letters
|
||||||
|
NUM_PASS_CHARS = string.digits
|
||||||
|
ALPHANUM_PASS_CHARS = ALPHA_PASS_CHARS + NUM_PASS_CHARS
|
||||||
|
SYMBOL_PASS_CHARS = string.punctuation
|
||||||
|
ALL_PASS_CHARS = ALPHANUM_PASS_CHARS + SYMBOL_PASS_CHARS
|
||||||
|
# These CAN be generated dynamically, see below.
|
||||||
|
CLIP_TIMEOUT = 45
|
||||||
|
SELECTED_PASS_CHARS = ALL_PASS_CHARS
|
||||||
|
SELECTED_PASS_NOSYMBOL_CHARS = ALPHANUM_PASS_CHARS
|
||||||
|
CLIPBOARD = 'clipboard'
|
||||||
|
GENERATED_LENGTH = 25 # I personally would prefer 32, but Pass compatability...
|
||||||
|
EDITOR = 'vi' # vi is on ...every? single distro and UNIX/UNIX-like, to my knowledge.
|
||||||
|
|
||||||
|
if not os.environ.get('NO_VAULTPASS_ENVS'):
|
||||||
|
# These are dynamically generated from the environment.
|
||||||
|
CLIP_TIMEOUT = int(os.environ.get('PASSWORD_STORE_CLIP_TIME', CLIP_TIMEOUT))
|
||||||
|
SELECTED_PASS_CHARS = os.environ.get('PASSWORD_STORE_CHARACTER_SET', SELECTED_PASS_CHARS)
|
||||||
|
SELECTED_PASS_NOSYMBOL_CHARS = os.environ.get('PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS',
|
||||||
|
SELECTED_PASS_NOSYMBOL_CHARS)
|
||||||
|
CLIPBOARD = os.environ.get('PASSWORD_STORE_X_SELECTION', CLIPBOARD)
|
||||||
|
GENERATED_LENGTH = int(os.environ.get('PASSWORD_STORE_GENERATED_LENGTH', GENERATED_LENGTH))
|
||||||
|
EDITOR = os.environ.get('EDITOR', EDITOR)
|
||||||
|
Reference in New Issue
Block a user