forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1Password lookup plugin (ansible#37207)
* add pytest_cache to gitignore * onepassword lookup plugin * fix linter/style test complaints * second pass at making pycodestyle happy * use json module instead of jq * update copyrights, license & version added * fix python2 compatibility * doh. fix spacing issue. * use standard ansible exception * remove potentially problematic stdin argument * actually call assertion method * add support for top-level fields * make vault uuids pedantically consistent in fixture * fix new style issues * ability specify section & correct case handling * improve error handling * add onepassword_raw plugin * Add maintainer info * Move common code to module_utils/onepassword.py * Load raw data JSON data for easier use in Ansible * Put OnePass class back inside lookup plugin There is no good place for sharing code across lookups currently. * Remove debugging code in unit tests * Patche proper module in raw unit tests * Add changelog entry Co-authored-by: Scott Buchanan <[email protected]>
- Loading branch information
Showing
6 changed files
with
525 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,7 @@ credentials.yml | |
# test output | ||
*.retry | ||
*.out | ||
.pytest_cache/ | ||
.tox | ||
.cache | ||
.pytest_cache | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
features: | ||
- onepassword lookup - add lookup plugins onepassword and onepassword_raw to retrieve secrets from 1Password vault (https://github.com/ansible/ansible/pull/37207) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# -*- coding: utf-8 -*- | ||
# (c) 2018, Scott Buchanan <[email protected]> | ||
# (c) 2016, Andrew Zenk <[email protected]> (lastpass.py used as starting point) | ||
# (c) 2018, Ansible Project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
ANSIBLE_METADATA = {'metadata_version': '1.1', | ||
'status': ['preview'], | ||
'supported_by': 'community'} | ||
|
||
DOCUMENTATION = """ | ||
lookup: onepassword | ||
author: | ||
- Scott Buchanan <[email protected]> | ||
- Andrew Zenk <[email protected]> | ||
version_added: "2.6" | ||
requirements: | ||
- C(op) 1Password command line utility. See U(https://support.1password.com/command-line/) | ||
- must have already logged into 1Password using C(op) CLI | ||
short_description: fetch field values from 1Password | ||
description: | ||
- onepassword wraps the C(op) command line utility to fetch specific field values from 1Password | ||
options: | ||
_terms: | ||
description: identifier(s) (UUID, name or domain; case-insensitive) of item(s) to retrieve | ||
required: True | ||
field: | ||
description: field to return from each matching item (case-insensitive) | ||
default: 'password' | ||
section: | ||
description: item section containing the field to retrieve (case-insensitive); if absent will return first match from any section | ||
default: None | ||
vault: | ||
description: vault containing the item to retrieve (case-insensitive); if absent will search all vaults | ||
default: None | ||
""" | ||
|
||
EXAMPLES = """ | ||
- name: "retrieve password for KITT" | ||
debug: | ||
msg: "{{ lookup('onepassword', 'KITT') }}" | ||
- name: "retrieve password for Wintermute" | ||
debug: | ||
msg: "{{ lookup('onepassword', 'Tessier-Ashpool', section='Wintermute') }}" | ||
- name: "retrieve username for HAL" | ||
debug: | ||
msg: "{{ lookup('onepassword', 'HAL 9000', field='username', vault='Discovery') }}" | ||
""" | ||
|
||
RETURN = """ | ||
_raw: | ||
description: field data requested | ||
""" | ||
|
||
import json | ||
import errno | ||
|
||
from subprocess import Popen, PIPE | ||
|
||
from ansible.plugins.lookup import LookupBase | ||
from ansible.errors import AnsibleLookupError | ||
|
||
|
||
class OnePass(object): | ||
|
||
def __init__(self, path='op'): | ||
self._cli_path = path | ||
|
||
@property | ||
def cli_path(self): | ||
return self._cli_path | ||
|
||
def assert_logged_in(self): | ||
try: | ||
self._run(["get", "account"]) | ||
except OSError as e: | ||
if e.errno == errno.ENOENT: | ||
raise AnsibleLookupError("1Password CLI tool not installed in path on control machine") | ||
raise e | ||
except AnsibleLookupError: | ||
raise AnsibleLookupError("Not logged into 1Password: please run 'op signin' first") | ||
|
||
def get_raw(self, item_id, vault=None): | ||
args = ["get", "item", item_id] | ||
if vault is not None: | ||
args += ['--vault={0}'.format(vault)] | ||
output, dummy = self._run(args) | ||
return output | ||
|
||
def get_field(self, item_id, field, section=None, vault=None): | ||
output = self.get_raw(item_id, vault) | ||
return self._parse_field(output, field, section) if output != '' else '' | ||
|
||
def _run(self, args, expected_rc=0): | ||
p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE) | ||
out, err = p.communicate() | ||
rc = p.wait() | ||
if rc != expected_rc: | ||
raise AnsibleLookupError(err) | ||
return out, err | ||
|
||
def _parse_field(self, data_json, field_name, section_title=None): | ||
data = json.loads(data_json) | ||
if section_title is None: | ||
for field_data in data['details'].get('fields', []): | ||
if field_data.get('name').lower() == field_name.lower(): | ||
return field_data.get('value', '') | ||
for section_data in data['details'].get('sections', []): | ||
if section_title is not None and section_title.lower() != section_data['title'].lower(): | ||
continue | ||
for field_data in section_data.get('fields', []): | ||
if field_data.get('t').lower() == field_name.lower(): | ||
return field_data.get('v', '') | ||
return '' | ||
|
||
|
||
class LookupModule(LookupBase): | ||
|
||
def run(self, terms, variables=None, **kwargs): | ||
op = OnePass() | ||
|
||
op.assert_logged_in() | ||
|
||
field = kwargs.get('field', 'password') | ||
section = kwargs.get('section') | ||
vault = kwargs.get('vault') | ||
|
||
values = [] | ||
for term in terms: | ||
values.append(op.get_field(term, field, section, vault)) | ||
return values |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# -*- coding: utf-8 -*- | ||
# (c) 2018, Scott Buchanan <[email protected]> | ||
# (c) 2016, Andrew Zenk <[email protected]> (lastpass.py used as starting point) | ||
# (c) 2018, Ansible Project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
ANSIBLE_METADATA = {'metadata_version': '1.1', | ||
'status': ['preview'], | ||
'supported_by': 'community'} | ||
|
||
DOCUMENTATION = """ | ||
lookup: onepassword_raw | ||
author: | ||
- Scott Buchanan <[email protected]> | ||
- Andrew Zenk <[email protected]> | ||
version_added: "2.6" | ||
requirements: | ||
- C(op) 1Password command line utility. See U(https://support.1password.com/command-line/) | ||
- must have already logged into 1Password using op CLI | ||
short_description: fetch raw json data from 1Password | ||
description: | ||
- onepassword_raw wraps C(op) command line utility to fetch an entire item from 1Password | ||
options: | ||
_terms: | ||
description: identifier(s) (UUID, name, or domain; case-insensitive) of item(s) to retrieve | ||
required: True | ||
vault: | ||
description: vault containing the item to retrieve (case-insensitive); if absent will search all vaults | ||
default: None | ||
""" | ||
|
||
EXAMPLES = """ | ||
- name: "retrieve all data about Wintermute" | ||
debug: | ||
msg: "{{ lookup('onepassword_raw', 'Wintermute') }}" | ||
""" | ||
|
||
RETURN = """ | ||
_raw: | ||
description: field data requested | ||
""" | ||
|
||
import json | ||
|
||
from ansible.plugins.lookup.onepassword import OnePass | ||
from ansible.plugins.lookup import LookupBase | ||
|
||
|
||
class LookupModule(LookupBase): | ||
|
||
def run(self, terms, variables=None, **kwargs): | ||
op = OnePass() | ||
|
||
op.assert_logged_in() | ||
|
||
vault = kwargs.get('vault') | ||
|
||
values = [] | ||
for term in terms: | ||
data = json.loads(op.get_raw(term, vault)) | ||
values.append(data) | ||
return values |
Oops, something went wrong.