Warning! kbs2
is alpha-quality software! Using kbs2
means accepting that your secrets may be lost or compromised at any time!
kbs2
is a command line utility for managing secrets.
kbs2
uses the age Rust crate by default, although it can be
configured to use any age-compatible CLI.
Quick links:
kbs2
is most easily installed via cargo
:
bash
$ cargo install kbs2
After installation, kbs2
is completely ready for use. See the
Configuration section for some optional changes that you can
make, like switching out the use of the age crate
for an age
-compatible CLI.
Initialize a new kbs2
configuration:
bash
$ kbs2 init
kbs2 init
will automatically discover an appropriate age CLI and generate a keypair with it.
Create a new login record:
bash
$ kbs2 new login amazon
Username? jonf-bonzo
Password? (hidden)
List available records:
bash
$ kbs2 list
amazon
facebook
Pull the password from a record:
```bash $ kbs2 pass -c amazon
$ kbs2 pass facebook | pbcopy ```
Remove a record:
bash
$ kbs2 rm facebook
kbs2
's subcommands are substantially more featured than the above examples demonstrate;
run each with --help
to see a full set of supported options.
kbs2 init
``` initialize kbs2 with a new config and keypair
USAGE: kbs2 init [FLAGS]
FLAGS: -f, --force overwrite the config and keyfile, if already present -h, --help Prints help information --insecure-not-wrapped don't wrap the keypair with a master password ```
Create a new config and keypair, prompting the user for a master password:
bash
$ kbs2 init
Create a new config and keypair without a master password:
bash
$ kbs2 init --insecure-not-wrapped
kbs2 unlock
``` unwrap the private key for use
USAGE: kbs2 unlock
FLAGS: -h, --help Prints help information ```
Unwrap the private key, allowing future commands to run without the master password:
bash
$ kbs2 unlock
kbs2 lock
``` remove the unwrapped key, if any, from shared memory
USAGE: kbs2 lock
FLAGS: -h, --help Prints help information ```
Remove the unwrapped private key from shared memory, requiring future commands to prompt for the master password:
bash
$ kbs2 lock
kbs2 new
``` create a new record
USAGE:
kbs2 new [FLAGS] [OPTIONS]
ARGS:
FLAGS: -f, --force overwrite, if already present -g, --generate generate sensitive fields instead of prompting for them -h, --help Prints help information -t, --terse read fields in a terse format, even when connected to a tty
OPTIONS:
-G, --generator
Create a new login
record named foobar
:
bash
$ kbs2 new login foobar
Username: hasdrubal
Password: [hidden]
Create a new environment
record named twitter-api
, overwriting it if it already exists:
bash
$ kbs2 new -f environment twitter-api
Variable: TWITTER_API
Value: [hidden]
Create a new login
record named pets.com
, generating the password with the default generator:
bash
$ kbs2 new -g login pets.com
Username: hasdrubal
Create a new login
record named email
, getting the fields in a terse format:
bash
$ kbs2 new -t login email < <(echo -e "bill@microsoft.com\x01hunter2")
kbs2 list
``` list records
USAGE: kbs2 list [FLAGS] [OPTIONS]
FLAGS: -d, --details print (non-field) details for each record -h, --help Prints help information
OPTIONS:
-k, --kind
List all records, one per line:
bash
$ kbs2 list
foobar
twitter-api
pets.com
email
List (non-sensitive) details for each record:
bash
$ kbs2 list -d
foobar
Kind: login
Timestamp: 1590277900
twitter-api
Kind: environment
Timestamp: 1590277907
pets.com
Kind: login
Timestamp: 1590277920
email
Kind: login
Timestamp: 1590277953
List only environment records:
bash
$ kbs2 list -k environment
twitter-api
kbs2 rm
``` remove a record
USAGE: kbs2 rm
ARGS:
FLAGS: -h, --help Prints help information ```
Remove the foobar
record:
bash
$ kbs2 rm foobar
kbs2 dump
``` dump a record
USAGE: kbs2 dump [FLAGS]
ARGS:
FLAGS: -h, --help Prints help information -j, --json dump in JSON format ```
Dump the twitter-api
record:
bash
$ kbs2 dump twitter-api
Label: twitter-api
Kind: environment
Variable: TWITTER_API
Value: 92h2890fn83fb2378fbf283bf73fbxkfnso90
Dump the pets.com
record in JSON format:
bash
$ kbs2 dump -j pets.com | json_pp
{
"kind" : "Login",
"timestamp" : 1590277920,
"label" : "pets.com",
"fields" : [
{
"name" : "username",
"value" : "hasdrubal"
},
{
"name" : "password",
"value" : "hunter2"
}
]
}
kbs2 pass
``` get the password in a login record
USAGE: kbs2 pass [FLAGS]
ARGS:
FLAGS: -c, --clipboard copy the password to the clipboard -h, --help Prints help information ```
Get the password for the pets.com
record:
bash
$ kbs2 pass pets.com
hunter2
Copy the password for the pets.com
record into the clipboard:
bash
$ kbs2 pass -c pets.com
kbs2 env
``` get an environment record
USAGE: kbs2 env [FLAGS]
ARGS:
FLAGS:
-h, --help Prints help information
-n, --no-export print only VAR=val without export
-v, --value-only print only the environment variable value, not the variable name
```
Get an environment record in export
-able form:
bash
$ kbs2 env twitter-api
export TWITTER_API=92h2890fn83fb2378fbf283bf73fbxkfnso90
Get just the value in an environment record:
bash
$ kbs2 env -v twitter-api
92h2890fn83fb2378fbf283bf73fbxkfnso90
kbs2 edit
``` modify a record with a text editor
USAGE: kbs2 edit [FLAGS]
ARGS:
FLAGS: -h, --help Prints help information -p, --preserve-timestamp don't update the record's timestamp ```
Open the email
record for editing:
bash
$ kbs2 edit email
Open the email
record for editing with a custom $EDITOR
:
bash
$ EDITOR=vim kbs2 edit email
kbs2
stores its configuration in <config dir>/kbs2/kbs2.conf
, where <config dir>
is determined
by your host system. On Linux, for example, it's ~/.config
.
kbs2.conf
is TOML-formatted, and might look something like this after a clean start with kbs2 init
:
```toml age-backend = "RageLib" public-key = "age1elujxyndwy0n9j2e2elmk9ns8vtltg69q620dr0sz4nu5fgj95xsl2peea" keyfile = "/home/william/.config/kbs2/key" store = "/home/william/.local/share/kbs2"
[commands.pass] clipboard-duration = 10 clear-after = true x11-clipboard = "Clipboard" ```
age-backend
(default: "RageLib"
)The age-backend
setting tells kbs2
how to operate on age-formatted keypairs and encrypted
records. The supported options are "RageLib"
, "AgeCLI
", and "RageCLI"
:
"RageLib"
: Use the age crate for all age operations. This is the default
setting, and offers the best performance.
"AgeCLI"
: Use the age
and age-keygen
binaries for all all age operations. This setting
requires that age
and age-keygen
are already installed; see the
age README for instructions.
"RageCLI"
: Use the rage
and rage-keygen
binaries for all age operations. This setting
requires that rage
and rage-keygen
are already installed; see the
rage README for instructions.
public-key
(default: generated by kbs2 init
)The public-key
setting records the public half of the age keypair used by kbs2
.
kbs2 init
pre-populates this setting; users should not modify it unless also modifying
the keyfile
setting (e.g., to point to a pre-existing age keypair).
keyfile
(default: generated by kbs2 init
)The keyfile
setting records the path to the private half of the age keypair used by kbs2
.
kbs2 init
pre-populates this setting; users should not modify it unless also modifying
the public-key
setting (e.g., to point to a pre-existing age keypair).
wrapped
(default: true
)The wrapped
settings records whether keyfile
is a "wrapped" private key, i.e. whether
the private key itself is encrypted with a master password.
By default, kbs2 init
asks the user for a master password and creates a wrapped key.
See the kbs2 init
documentation for more information.
store
(default: <user data directory>/kbs2
)The store
setting records the path to the secret store, i.e. where records are kept.
Users may modify this setting to store their records in custom directory.
pre-hook
(default: None
)The pre-hook
setting can be used to run a command before (almost) every kbs2
invocation.
Read the Hooks documentation for more details.
post-hook
(default: None
)The post-hook
setting can be used to run a command after (almost) every kbs2
invocation.
Read the Hooks documentation for more details.
reentrant-hooks
(default: false
)The reentrant-hooks
setting controls whether hooks are run multiple times when a hook itself
runs kbs2
. By default, hooks are run only for the initial kbs2
invocation.
Read the Reentrancy section of the Hooks documentation for more details.
commands.new.pre-hook
(default: None
)The commands.new.pre-hook
setting is like the global pre-hook
setting, except that it runs
immediately before record creation during kbs2 new
(and only kbs2 new
).
commands.new.post-hook
(default: None
)The commands.new.post-hook
setting is like the global post-hook
setting, except that it runs
immediately after record creation during kbs2 new
(and only kbs2 new
).
The commands.new.post-hook
setting passes a single argument to its hook, which is the label
of the record that was just created. For example, the following:
toml
[commands.new]
post-hook = "~/.config/kbs2/hooks/post-new.sh"
```bash
&2 echo "[+] created ${1}" ```
would produce:
bash
$ kbs2 new login foo
Username: bar
Password: [hidden]
[+] created foo
commands.pass.clipboard-duration
(default: 10
)The commands.pass.clipboard-duration
setting determines the duration, in seconds, for persisting
a password stored in the clipboard via kbs2 pass -c
.
commands.pass.clear-after
(default: true
)The commands.pass.clear-after
setting determines whether or not the clipboard is cleared at
all after kbs2 pass -c
.
Setting this to false
overrides any duration configured in commands.pass.clipboard-duration
.
commands.pass.x11-clipboard
(default: "Clipboard"
)This setting has no functionality yet; see #3.
The commands.pass.x11-clipboard
setting determines which clipboard is used on X11.
Valid options are "Clipboard"
and "Primary"
.
commands.pass.pre-hook
(default: None
)The command.pass.pre-hook
setting is like the global pre-hook
setting, except that it runs
immediately before record access during kbs2 pass
(and only kbs2 pass
).
command.pass.post-hook
(default: None
)The command.pass.post-hook
setting is like the global post-hook
setting, except that it runs
immediately after record access during kbs2 pass
(and only kbs2 pass
).
command.pass.clear-hook
(default: None
)The command.pass.clear-hook
is like the other command.pass
hooks, except that it only runs
after the password has been cleared from the clipboard.
commands.edit.editor
(default: None
)The commands.edit.editor
setting controls which editor is used when opening a file with
kbs2 edit
. The $EDITOR
environment variable takes precedence over this setting.
This setting is allowed to contain flags. For example, the following would be split correctly:
toml
[commands.edit]
editor = "subl -w"
commands.rm.post-hook
(default: None
)The command.rm.post-hook
setting is like the global post-hook
setting, except that it runs
immediately after record removal during kbs2 rm
(and only kbs2 rm
).
kbs2
supports generators for producing sensitive values, allowing users to automatically
generate passwords and environment variables.
Generators come in two flavors: "command" generators and "internal" generators. Both are
configured as entries in [[generators]]
.
The following configures two generators: a "command" generator named "pwgen" that executes
pwgen
to get a new secret, and an "internal" generator named "hexonly" that generates
a secret from the configured alphabet and length.
```toml [[generators]] name = "pwgen" command = "pwgen 16 1"
[[generators]] name = "hexonly" alphabet = "0123456789abcdef" length = 16 ```
These generators can be used with kbs2 new
:
```bash
$ kbs2 new -gG hexonly login pets.com Username: catlover2000 ```
Beyond the configuration above, kbs2
offers several avenues for customization.
kbs2
supports git
-style subcommands, allowing you to easily write your own.
For example, running the following:
$ kbs2 frobulate --xyz
will cause kbs2
to run kbs2-frobulate --xyz
. Custom commands are allowed to read from and
write to the config file under the [commands.<name>]
hierarchy.
The kbs2-ext-cmds repository contains several useful external commands.
kbs2
exposes hook-points during the lifecycle of an invocation, allowing users to
inject additional functionality or perform their own bookkeeping.
All hooks, whether pre- or post-, have the following behavior:
stdin
or stdout
from the parent kbs2
processstderr
from the parent process, and may use it to print anything
they pleasestore
directoryKBS2_HOOK=1
in their environmentkbs2
command to failHooks may introduce additional behavior, so long as it does not conflict with the above. Any additional hook behavior is documented under that hook's configuration setting.
kbs2
's hooks are non-reentrant by default.
To understand what that means, imagine the following hook setup:
toml
pre-hook = "~/.config/kbs2/hooks/pre.sh"
```bash
kbs2 some-other-command ```
and then:
bash
$ kbs2 list
In this setting, most users would expect pre.sh
to be run exactly once: on kbs2 list
.
However, naively, it ought to execute twice: once for kbs2 list
, and again for
kbs2 some-other-command
. In other words, naively, hooks would reenter themselves whenever
they use kbs2
internally.
Most users find this confusing and would consider it an impediment to hook writing, so kbs2
does not do this by default. However, should you wish for reentrant hooks, you have two
options:
reentrant-hooks
to true
in the configuration. This will make all hooks
reentrant — it's all or nothing, intentionally.unset
or otherwise delete the KBS2_HOOK
environment variable in your hook
before running kbs2
internally. This allows you to control which hooks cause reentrancy.
Beware: KBS2_HOOK
is an implementation detail! Unset it at your own risk!No good reason. See the history section.
TL;DR: kbs2
is short for "KBSecret 2".
In 2017, I wrote KBSecret as a general purpose secret manager for the Keybase ecosystem.
KBSecret was written in Ruby and piggybacked off of Keybase + KBFS for encryption, storage, and synchronization. It was also extremely flexible, allowing user-defined record types, secret sharing between users and teams, and a variety of convenient and well-behaved CLI tools for integration into my development ecosystem.
Unfortunately, KBSecret was also extremely slow: it was written in obnoxiously metaprogrammed Ruby, relied heavily on re-entrant CLIs, and was further capped by the latency and raw performance of KBFS itself.
Having a slow secret manager was fine for my purposes, but I no longer trust that Keybase (and KBFS) will continue to receive the work they require. I also no longer have the time to maintain KBSecret's (slowly) deteriorating codebase.
kbs2
is my attempt to reproduce the best parts of KBSecret in a faster language. Apart from the
name and some high-level design decisions, it shares nothing in common with the original KBSecret.
It's only named kbs2
because I'm used to typing "kbs" in my terminal.