Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make bitwarden lookup installable with ansible-galaxy and add support for custom fields #1

Merged
merged 6 commits into from Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
118 changes: 111 additions & 7 deletions README.md
@@ -1,18 +1,122 @@
# ansible-modules-bitwarden

Bitwarden integrations for Ansible
Bitwarden integration for Ansible.

## Installation

The easiest way to install this lookup plugin is to use the
`ansible-galaxy` command:

ansible-galaxy install git+https://github.com/c0sco/ansible-modules-bitwarden

This will place the `ansible-modules-bitwarden` role into
`$HOME/.ansible/roles`, where it will be available to all playbooks
you run on your system.

## Lookup plugin

Use `lookup()` with the `bitwarden` argument, followed by the items you want to retrieve. The default field is `password`, but any other field can be specified. If you need to specify the path to the Bitwarden CLI binary, use the `path` named argument. For example:
To use this plugin, you will need to activate it by including the role
in your play. For example:

- hosts: localhost
roles:
- ansible-modules-bitwarden

Use Ansible's `lookup()` function with the `bitwarden` argument,
followed by the items you want to retrieve. The default field is
`password`, but any other field can be specified using the `field`
named argument. If you need to specify the path to the Bitwarden CLI
binary, use the `path` named argument.

## Examples

### Get a single password

```yaml
# Get username for Slashdot and Google
- debug: msg="{{ lookup('bitwarden', 'Slashdot', 'Google', field='username', path='/not/in/my/path/bw') }}"
# Get password for Google
- debug:
msg: {{ lookup('bitwarden', 'Google') }}
```

If you want to run the plugin directly for testing, you must first specify the field, then list the items to fetch.
The above might result in:

```bash
lib/ansible/plugins/lookup/bitwarden.py username Google Slashdot
```
TASK [debug] *********************************************************
ok: [localhost] => {
"msg": "mysecret"
}
```

### Get a single username

```yaml
# Get username for Google
- debug:
msg: {{ lookup('bitwarden', 'Google', field='username' }}
```

The above might result in:

```
TASK [debug] *********************************************************
ok: [localhost] => {
"msg": "alice"
}
```

### See all available fields

```yaml
# Get all available fields for an entry
- debug:
msg: {{ lookup('bitwarden', 'Google', field='item' }}
```

The above might result in:

```
TASK [debug] *********************************************************
ok: [localhost] => {
"msg": {
"favorite": false,
"fields": [
{
"name": "mycustomfield",
"type": 0,
"value": "the value of my custom field"
}
],
"folderId": null,
"id": "12345678-0123-4321-0000-a97001342c31",
"login": {
"password": "mysecret",
"passwordRevisionDate": null,
"totp": null,
"username": "alice"
},
"name": "Google",
"notes": null,
"object": "item",
"organizationId": "87654321-1234-9876-0000-a96800ed2b47",
"revisionDate": "2018-10-19T19:20:17.923Z",
"type": 1
}
}
```

### Get the value of a custom field

```yaml
# Get the value of a custom field
- debug:
msg: {{ lookup('bitwarden', 'Google', field='mycustomfield', custom_field=true }}
```

The above might result in:

```
TASK [debug] *********************************************************
ok: [localhost] => {
"msg": "the value of my custom field"
}
```
@@ -1,14 +1,31 @@
#!/usr/bin/env python

# (c) 2018, Matt Stofko <matt@mjslabs.com>
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
# GNU General Public License v3.0+ (see LICENSE or
# https://www.gnu.org/licenses/gpl-3.0.txt)
#
# This plugin can be run directly by specifying the field followed by a list of entries, e.g.
# bitwarden.py password google.com wufoo.com
# This plugin can be run directly by specifying the field followed by a list of
# entries, e.g. bitwarden.py password google.com wufoo.com
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import json
import os
import sys

from subprocess import Popen, PIPE, check_output

from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase

try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()


DOCUMENTATION = """
lookup: bitwarden
author:
Expand All @@ -18,14 +35,20 @@
- BW_SESSION environment var (from `bw login` or `bw unlock`)
short_description: look up data from a bitwarden vault
description:
- use the bw command line utility to grab one or more items stored in a bitwarden vault
- use the bw command line utility to grab one or more items stored in a
bitwarden vault
options:
_terms:
description: name of item that contains the field to fetch
required: True
field:
description: field to return from bitwarden
default: 'password'
custom_field:
description: If True, look up named field in custom fields instead
of top-level dictionary.
sync:
description: If True, call `bw sync` before lookup
"""

EXAMPLES = """
Expand All @@ -40,18 +63,6 @@
- Items from Bitwarden vault
"""

from subprocess import Popen, PIPE, check_output
import os, sys

from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase

try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()


class Bitwarden(object):

Expand All @@ -75,21 +86,34 @@ def _run(self, args):
out, err = p.communicate()
rc = p.wait()
if rc != 0:
display.debug("Received error when running '{0} {1}': {2}".format(self.cli_path, args, out))
display.debug("Received error when running '{0} {1}': {2}"
.format(self.cli_path, args, out))
if out.startswith("Vault is locked."):
raise AnsibleError("Error accessing Bitwarden vault. Run 'bw unlock' to unlock the vault.")
raise AnsibleError("Error accessing Bitwarden vault. "
"Run 'bw unlock' to unlock the vault.")
elif out.startswith("You are not logged in."):
raise AnsibleError("Error accessing Bitwarden vault. Run 'bw login' to login.")
raise AnsibleError("Error accessing Bitwarden vault. "
"Run 'bw login' to login.")
elif out.startswith("Failed to decrypt."):
raise AnsibleError("Error accessing Bitwarden vault. Make sure BW_SESSION is set properly.")
raise AnsibleError("Error accessing Bitwarden vault. "
"Make sure BW_SESSION is set properly.")
elif out.startswith("Not found."):
raise AnsibleError("Error accessing Bitwarden vault. Specified item not found.")
raise AnsibleError("Error accessing Bitwarden vault. "
"Specified item not found.")
else:
raise AnsibleError("Unknown failure in 'bw' command: {0}".format(out))
raise AnsibleError("Unknown failure in 'bw' command: "
"{0}".format(out))
return out.strip()

def sync(self):
self._run(['sync'])

def get_entry(self, key, field):
return self._run(["get", field, key])
return self._run(["get", field, key]).decode('utf-8')

def get_custom_field(self, key, field):
data = json.loads(self.get_entry(key, 'item'))
return next(x for x in data['fields'] if x['name'] == field)['value']


class LookupModule(LookupBase):
Expand All @@ -98,18 +122,28 @@ def run(self, terms, variables=None, **kwargs):
bw = Bitwarden(path=kwargs.get('path', 'bw'))

if not bw.logged_in:
raise AnsibleError("Not logged into Bitwarden: please run 'bw login', or 'bw unlock' and set the BW_SESSION environment variable first")
raise AnsibleError("Not logged into Bitwarden: please run "
"'bw login', or 'bw unlock' and set the "
"BW_SESSION environment variable first")

field = kwargs.get('field', 'password')
values = []

if kwargs.get('sync'):
bw.sync()

for term in terms:
values.append(bw.get_entry(term, field))
if kwargs.get('custom_field'):
values.append(bw.get_custom_field(term, field))
else:
values.append(bw.get_entry(term, field))
return values


def main():
if len(sys.argv) < 3:
print("Usage: {0} <field> <name> [name name ...]".format(os.path.basename(__file__)))
print("Usage: {0} <field> <name> [name name ...]"
.format(os.path.basename(__file__)))
return -1

print(LookupModule().run(sys.argv[2:], None, field=sys.argv[1]))
Expand Down
6 changes: 6 additions & 0 deletions meta/main.yml
@@ -0,0 +1,6 @@
galaxy_info:
author: Matt Stofko
description: Ansible lookup module for interacting with BitWarden
license: GPLv3
min_ansible_version: 2.5
galaxy_tags: [password, secret, bitwarden, credentials]