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

ADD: Organization and Collection to lookup. #22

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ ok: [localhost] => {
}
```

### Get a single password use organization and collection

```
---
- hosts: localhost
roles:
- ansible-modules-bitwarden
tasks:
- debug:
msg: "{{ lookup('bitwarden', 'google', field='password', organization='my org', collection='shared accounts', sync=True) }}"
```

The above might result in:

```
TASK [Gathering Facts] *********************************************************
ok: [localhost]

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

### See all available fields

```yaml
Expand Down Expand Up @@ -109,7 +133,7 @@ ok: [localhost] => {
```yaml
# Get the value of a custom field
- debug:
msg: {{ lookup('bitwarden', 'Google', field='mycustomfield', custom_field=true) }}
msg: {{ lookup('bitwarden', 'Google', field='mycustomfield', type='custom') }}
```

The above might result in:
Expand All @@ -126,7 +150,7 @@ ok: [localhost] => {
```yaml
# Get the value of a custom field
- debug:
msg: {{ lookup('bitwarden', 'privateKey.pem', itemid='123456-1234-1234-abbf-60c345aaa3e', attachments=true ) }}
msg: {{ lookup('bitwarden', 'Google', field='privateKey.pem', type='attachment' ) }}
```
Optional parameters - output='/ansible/publicKey.pem'

Expand All @@ -135,6 +159,6 @@ The above might result in:
```
TASK [debug] *********************************************************
ok: [localhost] => {
"msg": "Saved /publicKey.pem"
"msg": "ssh-rsa xxxx foo@bar"
}
```
118 changes: 91 additions & 27 deletions lookup_plugins/bitwarden.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ def _run(self, args):
elif out.startswith("Not found."):
raise AnsibleError("Error accessing Bitwarden vault. "
"Specified item not found: {}".format(args[-1]))
elif out.startswith("More than one result was found."):
raise AnsibleError("Error accessing Bitwarden vault. "
"Specified item found more than once: {}".format(args[-1]))
else:
raise AnsibleError("Unknown failure in 'bw' command: "
"{0}".format(out))
Expand All @@ -132,21 +135,84 @@ def status(self):
raise AnsibleError("Error decoding Bitwarden status: %s" % e)
return data['status']

def get_entry(self, key, field):
return self._run(["get", field, key])
def organization(self, name):
try:
data = json.loads(self._run(['list', 'organizations']))
except json.decoder.JSONDecodeError as e:
raise AnsibleError("Error decoding Bitwarden list organizations: %s" % e)

if not isinstance(data, list):
raise AnsibleError("Error decoding Bitwarden list organizations no organization in list")

if len(data) == 0:
raise AnsibleError("Error decoding Bitwarden list organizations no organization in list")

def get_notes(self, key):
data = json.loads(self.get_entry(key, 'item'))
return data['notes']
for organization in data:
if 'id' in organization.keys() and 'name' in organization.keys() and organization['name'] == name:
return(organization['id'])

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']
raise AnsibleError("Error decoding Bitwarden list organizations no organization not found: %s" % name)

def get_attachments(self, key, itemid, output):
attachment = ['get', 'attachment', '{}'.format(
key), '--output={}'.format(output), '--itemid={}'.format(itemid)]
return self._run(attachment)
def collection(self, name):
try:
data = json.loads(self._run(['list', 'collections']))
except json.decoder.JSONDecodeError as e:
raise AnsibleError("Error decoding Bitwarden list collections: %s" % e)

if not isinstance(data, list):
raise AnsibleError("Error decoding Bitwarden list collections no collection in list")

if len(data) == 0:
raise AnsibleError("Error decoding Bitwarden list collections no collection in list")

for collection in data:
if 'id' in collection.keys() and 'name' in collection.keys() and collection['name'] == name:
return(collection['id'])

raise AnsibleError("Error decoding Bitwarden list collections no collection not found: %s" % name)

def get_entry(self, key, field, organizationId=None, collectionId=None, type='default'):
#return self._run(["get", field, key])
data = json.loads(self._run(['list', 'items', '--search', key]))
if not isinstance(data, list):
raise AnsibleError("Error decoding Bitwarden list items no item in list")

if len(data) == 0:
raise AnsibleError("Error decoding Bitwarden list items no item in list")

_return = []
for result in data:
if 'id' in result.keys() and 'name' in result.keys() and 'collectionIds' in result.keys() and 'organizationId' in result.keys():
if organizationId == None:
pass
elif result['organizationId'] != organizationId:
continue
if collectionId == None:
pass
elif collectionId not in result['collectionIds']:
continue

if type == 'default' and field == 'item':
_return.append(result)
elif type == 'default' and field == 'password':
_return.append(result['login']['password'])
elif type == 'default' and field == 'username':
_return.append(result['login']['username'])
elif type == 'custom' and 'fields' in result.keys() and any(field in x['name'] for x in result['fields']):
for x in result['fields']:
if x['name'] == field:
_return.append( x['value'])
elif type == 'attachment' and 'attachments' in result.keys() and any(field in x['fileName'] for x in result['attachments']):
for x in result['attachments']:
if x['fileName'] == field:
_return.append(self._run(['get', 'attachment', x['id'], '--quiet', '--raw', '--output', '/dev/stdout', '--itemid', result['id']]))
elif type == 'default' and field in result.keys():
_return.append(result[field])
if len(_return) > 1:
raise AnsibleError("Error decoding Bitwarden list items more then one item found for: %s" % field)
elif len(_return) == 1:
return _return[0]
raise AnsibleError("Error decoding Bitwarden list items no field not found: %s" % field)


class LookupModule(LookupBase):
Expand All @@ -160,29 +226,27 @@ def run(self, terms, variables=None, **kwargs):
"BW_SESSION environment variable first")

field = kwargs.get('field', 'password')
type = kwargs.get('type', 'default')
organization = kwargs.get('organization', None)
organizationId = None
collection = kwargs.get('collection', None)
collectionId = None
values = []

if organization != None:
organizationId = bw.organization(organization)

if collection != None:
collectionId = bw.collection(collection)

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

if kwargs.get('session'):
bw.session = kwargs.get('session')

for term in terms:
if kwargs.get('custom_field'):
values.append(bw.get_custom_field(term, field))
elif field == 'notes':
values.append(bw.get_notes(term))
elif kwargs.get('attachments'):
if kwargs.get('itemid'):
itemid = kwargs.get('itemid')
output = kwargs.get('output', term)
values.append(bw.get_attachments(term, itemid, output))
else:
raise AnsibleError("Missing value for - itemid - "
"Please set parameters as example: - "
"itemid='f12345-d343-4bd0-abbf-4532222' ")
else:
values.append(bw.get_entry(term, field))
values.append(bw.get_entry(term, field, organizationId, collectionId, type))
return values


Expand Down