bwv is a small helper application for serving Bitwarden secrets over HTTP(S).
Note. Much of the bitwarden API and cryptography functionality is drawn from the
bitwapplication (https://github.com/mvdan/bitw) however there is no association between the two applications and any issues found with this repo should be reported here.Due to the integrations with KWallet, Libsecret and SystemD, this application only works on the Linux platform today.
If someone knows how to integrate similar functionality on other platforms, I would welcome pull requests.
Disclaimer
I am not a cryptographer, nor do I make any such claim to being one; neither am I affiliated with Bitwarden in any way.
This code is a personal project to wrap the Bitwarden API and present secrets for use inside my environment but I am not responsible for the cryptographic functions used to encrypt secrets other than the wrappers you find in this library and application. If you have questions or concerns regarding how secrets are encrypted, please discuss these with with Bitwarden directly.
Whilst every effort is taken to ensure this application handles secrets in a secure manner during its normal operation, including the use of guarded memory, cryptographic shredding of passwords and hashes and ensuring decrypted secrets are kept in memory for the minimal amount of time, certain functionality provided as part of this application offers the opportinity for secrets to be leaked and should thus be used with caution.
This is no different than any other API or wallet service and the authentication for the API can (and should) be tied to a wallet service ensuring access to the server is not available for users outside of your own current session.
To configure bwv to access your Bitwarden account details, you may do this in
a number of ways:
- Environment
- kwallet
- libsecrets
In all instances, BW_CLIENTID and BW_CLIENTSECRET are optional but
recommended to prevent continuous prompting for 2fa
BW_CLIENTID
BW_CLIENTSECRET
BW_PASSWORD
BW_EMAIL
Create the environment variables containing the credentials.
Store the above secrets in kwallet at /Passwords/bwvault
Use the libsecrets manager of your choice and store the above secrets as
attributes at the same /Passwords/bwvault.
Clone this repo then run go build .
This repo uses pre-commit to run the lint gate
(golangci-lint), tests and modernize. After cloning, enable the hooks once:
pre-commit install # commit-stage: golangci-lint + go test
pre-commit install --hook-type pre-push # push-stage: modernize
The Go hooks call your system golangci-lint (v2) and go toolchains. Run the
full suite by hand at any time with pre-commit run --all-files.
Note: The commands listed here, including credential access, are only available to localhost. If you need to access credentials over the network, see the API documention below.
serveRun bwv server in foreground.serviceinstallInstall the userspace systemd servicestartStart the userspace systemd servicestopstop the userspace systemd servicestatusGet the status of the service (usually one of "running" or "dead")removeStop the userspace systemd service and remove it entirely.
keygenkey <ip address|cidr range>Create a 32 character random string to use as an API key, bound to the provided address or cidr rangerevoke <key>Revokes the given key. Future iterations will allow for an ip or range to be provided if the key is lost.
path/to/secret[?[field|property]=valueGet the secret at a given path, optionally followed by specific properties to read
Run the server
$ bwv serve
2022/04/03 07:20:33 Login complete
2022/04/03 07:20:36 Master password configured
2022/04/03 07:20:36 Loading config file /home/mproffitt/.config/bwv/server.yaml
2022/04/03 07:20:36 Sync complete
2022/04/03 07:20:36 Listening for secure connections on :6277 (whitelist [127.0.0.0/24])
Retrieve a credential
$ bwv example/test
[
{
"fields": {
"unseal-1": "abcdef",
"unseal-2": "123456"
},
"folder_id": "804e76c5-c7fe-4a4b-94ef-ae6700d79146",
"id": "2d29507c-72ef-493b-a09f-ae6700d83380",
"name": "test",
"password": "GGAPP$KoQ499hDCBHqvCxURzzS$3bp*A",
"revision_date": "2022-03-29T13:07:09.8566667Z",
"type": 1,
"username": "invalid@example.com"
}
]
The following wildcard patterns are currently supported
*,./*both of these return all credentials which do not have a folder*/*return all credentials in all folders*/namereturn all credentials with namenamein any folder
A future version may include more advanced search patterns.
When only a single credential is being returned, this can be filtered to only return certain properties and/or fields by adding http query options onto the end of the path.
Note: Do not use filters with wildcards as this may give unexpected results.
Note: If none of the requested properties or fields exist on the matched item, the whole item is returned instead - attachments included. This is deliberate: a request that matches nothing gives you everything rather than an empty object.
If only a single property or field is being returned, this will always be
identified as value in the resulting json object.
$ bwv example/test?property=password
{
"value": "GGAPP$KoQ499hDCBHqvCxURzzS$3bp*A"
}
Multiple properties/fields can be requested with the following examples being equivelant.
$ bwv example/test -p password -p username -f unseal-1 -f unseal-2
{
"password": "GGAPP$KoQ499hDCBHqvCxURzzS$3bp*A",
"unseal-1": "abcdef",
"unseal-2": "123456",
"username": "invalid@example.com"
}
$ bwv 'example/test?properties=username,password&fields=unseal-1,unseal-2'
{
"password": "GGAPP$KoQ499hDCBHqvCxURzzS$3bp*A",
"unseal-1": "abcdef",
"unseal-2": "123456",
"username": "invalid@example.com"
}
Identity items are returned with their populated attributes under identity,
and every attribute is addressable as a property.
$ bwv Me
[
{
"id": "33333333-3333-3333-3333-333333333333",
"identity": {
"country": "Spain",
"email": "me@example.com",
"firstname": "Martin",
"lastname": "Proffitt",
"passportnumber": "123456789",
"postalcode": "39687",
"title": "Mr",
"username": "notapipeline"
},
"name": "Me",
"revision_date": "2025-08-30T09:11:04.1234567Z",
"type": 3
}
]
$ bwv 'Me?properties=firstname,lastname'
{
"firstname": "Martin",
"lastname": "Proffitt"
}
The attribute names are the lowercased Bitwarden identity fields:
title, firstname, middlename, lastname, username, company, ssn,
passportnumber, licensenumber, email, phone, address1, address2,
address3, city, state, postalcode, country.
username resolves to the identity's username when the item has no login, so
?properties=username works the same way for both types.
Any item type can carry a note. It is returned as notes, and -n/--notes
is shorthand for asking for that property.
$ bwv example/test -n
{
"value": "unseal keys are in the safe"
}
--secure-notes restricts matches to Secure Note items, which is what you want
with a wildcard:
$ bwv 'choclab/*' --secure-notes
Localhost and the machine network is always whitelisted. This is done by reading the machine interfaces.
To whitelist other IPs or ranges, you need to generate tokens for them. This is
done by using the key gen command.
$ bwv key gen 192.168.1.8/30
HTTP only exists for running on a local network, behind a firewall where it cannot and should never be accessed from the outside world. It is serving your passwords and these should never be transmitted in plaintext, even when you trust the requesting device.
I do not recommend using the HTTP only version, even for local connections. Setting up a local CA and certificates is simple and cheap, and if you need to serve externally, letsencrypt is your friend.
When connecting to the bwv server from any address other than localhost, an
API token is required.
Localhost uses either your BW_CLIENTSECRET or BW_PASSWORD to achieve this
with the preference being BW_CLIENTSECRET.
An api token is a random 32 character string which is stored encrypted in the
server configuration. The encryption uses a pbkdf2 key derived from your
master password. You are given the plaintext string which should be submitted as
a Bearer token when accessing the api. See below.
To generate an API token, use the genkey command.
$ bwv genkey 192.168.1.5
2022/04/03 07:55:26 Login complete
2022/04/03 07:55:29 Master password configured
2022/04/03 07:55:29 Loading config file /home/mproffitt/.config/bwv/server.yaml
========================================
token = TQ5d0IEyOEPAtgZmV76oOc0WqpU5VdDO
========================================
Tokens can be revoked either by specifying the token, or the address the token is associated with however if the address is part of a range, the token must be used.
$ bwv revoke 192.168.1.5
2022/04/03 07:58:21 Login complete
2022/04/03 07:58:23 Master password configured
2022/04/03 07:58:23 Loading config file /home/mproffitt/.config/bwv/server.yaml
For more advanced configuration, create or edit the file at
${HOME}/.config/bwv/server.yaml in which the following properties are allowed:
whitelistA list of IP addresses allowed to access the servicecertAn SSL certificate to secure your credentials in transitkeyThe SSL certificates keyportThe port to listen on. This must be above 1024 if running in userspace. If port is 0, defaults to 6277apikeysYou generally do not want to touch this map. Use./bwv genkeyand./bwv revoketo manage this.
The API for this application is simple.
genkey[INTERNAL]revokekey[INTERNAL]/reload[INTERNAL] Tells the server to reload its config. Normally you do not need to access this endpoint./path/to/credentialget the full contents atpath/to/credential./path/?property=username[,password]A top level attribute from the credential such as username or password. For identity items this is any of the identity attributes (firstname,postalcode, ...)./path/?field=my-custom-field[,another-field]Fields are custom attributes set on the credential.
All API calls must be made with an API token passed along with the request as a Bearer token.
$ curl -s -H "Authorization: Bearer TQ5d0IEyOEPAtgZmV76oOc0WqpU5VdDO" \
https://example.com:6277/example/test
Failure to provide a token, using anything other than Bearer, or using a token
not assigned to the address or range you are accessing the API from, will result
in a 403 Permission Denied response.
github.com/notapipeline/bwv/pkg/bwv is a client for a running bwv server. It
owns the handshake - reading the server's KDF parameters and encrypting the API
token with a key derived from your master password - so an embedding
application only has to ask for a path.
import "github.com/notapipeline/bwv/pkg/bwv"
c, err := bwv.NewClient(bwv.Options{})
if err != nil {
return err
}
// whole items, decrypted
items, err := c.Get(ctx, "choclab/customers/giantswarm")
company := items[0].Identity["company"]
// or only the values you want
values, err := c.GetProperties(ctx, "example/test", "username", "password")
fields, err := c.GetFields(ctx, "example/test", "unseal-1", "unseal-2")
files, err := c.GetAttachments(ctx, "example/test", "id_rsa")The zero Options talks to a server on localhost:6277, taking anything else
it needs from ~/.config/bwv/client.yaml and the credential store. Credentials
are resolved on the first request, so constructing a client never blocks on the
daemon being up. Set Retry if you want it to ride out a server that is still
starting; by default a request is attempted once.
GetProperties, GetFields and GetAttachments return ErrNoSelection rather
than the whole item when none of the requested names exist on it, and
ErrMultipleItems when the path matched more than one item - use Get for
wildcards. Raw is available for the server's own response shapes, which is
what the bwv command itself renders.