mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Support 1Password Connect (#5588) (#7116)
* Support 1Password Connect (#5588)
- Support 1Password Connect with the opv2 client
* Follow pep8, be less explicit
* Update changelog to include PR
* 1Password Connect host and token are now also parameters
* Get argument values from the environment or lookup arguments
* Move imports
* Force using Connect token and host at the same time
* Update unit tests
* Update documentation
* Additional tests
(cherry picked from commit f8652571f7
)
Co-authored-by: Xeryus Stokkel <XeryusTC@users.noreply.github.com>
This commit is contained in:
parent
327777a1da
commit
a76537b24f
4 changed files with 104 additions and 6 deletions
3
changelogs/fragments/5588-support-1password-connect.yml
Normal file
3
changelogs/fragments/5588-support-1password-connect.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
minor_changes:
|
||||
- onepassword lookup plugin - support 1Password Connect with the opv2 client by setting the connect_host and connect_token parameters (https://github.com/ansible-collections/community.general/pull/7116).
|
||||
- onepassword_raw lookup plugin - support 1Password Connect with the opv2 client by setting the connect_host and connect_token parameters (https://github.com/ansible-collections/community.general/pull/7116)
|
|
@ -52,6 +52,18 @@ DOCUMENTATION = '''
|
|||
- Only works with 1Password CLI version 2 or later.
|
||||
type: str
|
||||
version_added: 7.1.0
|
||||
connect_host:
|
||||
description: The host for 1Password Connect. Must be used in combination with O(connect_token).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_HOST
|
||||
version_added: 8.1.0
|
||||
connect_token:
|
||||
description: The token for 1Password Connect. Must be used in combination with O(connect_host).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_TOKEN
|
||||
version_added: 8.1.0
|
||||
vault:
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
|
||||
notes:
|
||||
|
@ -119,7 +131,7 @@ import json
|
|||
import subprocess
|
||||
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.errors import AnsibleLookupError, AnsibleOptionsError
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils.six import with_metaclass
|
||||
|
@ -139,6 +151,8 @@ class OnePassCLIBase(with_metaclass(abc.ABCMeta, object)):
|
|||
master_password=None,
|
||||
service_account_token=None,
|
||||
account_id=None,
|
||||
connect_host=None,
|
||||
connect_token=None,
|
||||
):
|
||||
self.subdomain = subdomain
|
||||
self.domain = domain
|
||||
|
@ -147,6 +161,8 @@ class OnePassCLIBase(with_metaclass(abc.ABCMeta, object)):
|
|||
self.secret_key = secret_key
|
||||
self.service_account_token = service_account_token
|
||||
self.account_id = account_id
|
||||
self.connect_host = connect_host
|
||||
self.connect_token = connect_token
|
||||
|
||||
self._path = None
|
||||
self._version = None
|
||||
|
@ -325,6 +341,10 @@ class OnePassCLIv1(OnePassCLIBase):
|
|||
return not bool(rc)
|
||||
|
||||
def full_signin(self):
|
||||
if self.connect_host or self.connect_token:
|
||||
raise AnsibleLookupError(
|
||||
"1Password Connect is not available with 1Password CLI version 1. Please use version 2 or later.")
|
||||
|
||||
if self.service_account_token:
|
||||
raise AnsibleLookupError(
|
||||
"1Password CLI version 1 does not support Service Accounts. Please use version 2 or later.")
|
||||
|
@ -510,6 +530,9 @@ class OnePassCLIv2(OnePassCLIBase):
|
|||
return ""
|
||||
|
||||
def assert_logged_in(self):
|
||||
if self.connect_host and self.connect_token:
|
||||
return True
|
||||
|
||||
if self.service_account_token:
|
||||
args = ["whoami"]
|
||||
environment_update = {"OP_SERVICE_ACCOUNT_TOKEN": self.service_account_token}
|
||||
|
@ -569,6 +592,15 @@ class OnePassCLIv2(OnePassCLIBase):
|
|||
if vault is not None:
|
||||
args += ["--vault={0}".format(vault)]
|
||||
|
||||
if self.connect_host and self.connect_token:
|
||||
if vault is None:
|
||||
raise AnsibleLookupError("'vault' is required with 1Password Connect")
|
||||
environment_update = {
|
||||
"OP_CONNECT_HOST": self.connect_host,
|
||||
"OP_CONNECT_TOKEN": self.connect_token,
|
||||
}
|
||||
return self._run(args, environment_update=environment_update)
|
||||
|
||||
if self.service_account_token:
|
||||
if vault is None:
|
||||
raise AnsibleLookupError("'vault' is required with 'service_account_token'")
|
||||
|
@ -592,7 +624,7 @@ class OnePassCLIv2(OnePassCLIBase):
|
|||
|
||||
class OnePass(object):
|
||||
def __init__(self, subdomain=None, domain="1password.com", username=None, secret_key=None, master_password=None,
|
||||
service_account_token=None, account_id=None):
|
||||
service_account_token=None, account_id=None, connect_host=None, connect_token=None):
|
||||
self.subdomain = subdomain
|
||||
self.domain = domain
|
||||
self.username = username
|
||||
|
@ -600,6 +632,8 @@ class OnePass(object):
|
|||
self.master_password = master_password
|
||||
self.service_account_token = service_account_token
|
||||
self.account_id = account_id
|
||||
self.connect_host = connect_host
|
||||
self.connect_token = connect_token
|
||||
|
||||
self.logged_in = False
|
||||
self.token = None
|
||||
|
@ -612,7 +646,8 @@ class OnePass(object):
|
|||
for cls in OnePassCLIBase.__subclasses__():
|
||||
if cls.supports_version == version.split(".")[0]:
|
||||
try:
|
||||
return cls(self.subdomain, self.domain, self.username, self.secret_key, self.master_password, self.service_account_token, self.account_id)
|
||||
return cls(self.subdomain, self.domain, self.username, self.secret_key, self.master_password, self.service_account_token,
|
||||
self.account_id, self.connect_host, self.connect_token)
|
||||
except TypeError as e:
|
||||
raise AnsibleLookupError(e)
|
||||
|
||||
|
@ -677,8 +712,13 @@ class LookupModule(LookupBase):
|
|||
master_password = self.get_option("master_password")
|
||||
service_account_token = self.get_option("service_account_token")
|
||||
account_id = self.get_option("account_id")
|
||||
connect_host = self.get_option("connect_host")
|
||||
connect_token = self.get_option("connect_token")
|
||||
|
||||
op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token, account_id)
|
||||
if (connect_host or connect_token) and None in (connect_host, connect_token):
|
||||
raise AnsibleOptionsError("connect_host and connect_token are required together")
|
||||
|
||||
op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token, account_id, connect_host, connect_token)
|
||||
op.assert_logged_in()
|
||||
|
||||
values = []
|
||||
|
|
|
@ -49,6 +49,18 @@ DOCUMENTATION = '''
|
|||
- Only works with 1Password CLI version 2 or later.
|
||||
type: string
|
||||
version_added: 7.1.0
|
||||
connect_host:
|
||||
description: The host for 1Password Connect. Must be used in combination with O(connect_token).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_HOST
|
||||
version_added: 8.1.0
|
||||
connect_token:
|
||||
description: The token for 1Password Connect. Must be used in combination with O(connect_host).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_TOKEN
|
||||
version_added: 8.1.0
|
||||
vault:
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
|
||||
notes:
|
||||
|
@ -86,6 +98,7 @@ RETURN = """
|
|||
import json
|
||||
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword import OnePass
|
||||
from ansible.errors import AnsibleOptionsError
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
|
@ -102,8 +115,13 @@ class LookupModule(LookupBase):
|
|||
master_password = self.get_option("master_password")
|
||||
service_account_token = self.get_option("service_account_token")
|
||||
account_id = self.get_option("account_id")
|
||||
connect_host = self.get_option("connect_host")
|
||||
connect_token = self.get_option("connect_token")
|
||||
|
||||
op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token, account_id)
|
||||
if (connect_host or connect_token) and None in (connect_host, connect_token):
|
||||
raise AnsibleOptionsError("connect_host and connect_token are required together")
|
||||
|
||||
op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token, account_id, connect_host, connect_token)
|
||||
op.assert_logged_in()
|
||||
|
||||
values = []
|
||||
|
|
|
@ -18,7 +18,7 @@ from .onepassword_conftest import ( # noqa: F401, pylint: disable=unused-import
|
|||
)
|
||||
from .onepassword_common import MOCK_ENTRIES
|
||||
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.errors import AnsibleLookupError, AnsibleOptionsError
|
||||
from ansible.plugins.loader import lookup_loader
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword import (
|
||||
OnePassCLIv1,
|
||||
|
@ -82,6 +82,12 @@ def test_assert_logged_in_v2(mocker, args, out, expected_call_args, expected_cal
|
|||
assert result == expected
|
||||
|
||||
|
||||
def test_assert_logged_in_v2_connect():
|
||||
op_cli = OnePassCLIv2(connect_host="http://localhost:8080", connect_token="foobar")
|
||||
result = op_cli.assert_logged_in()
|
||||
assert result
|
||||
|
||||
|
||||
def test_full_signin_v2(mocker):
|
||||
mocker.patch.object(OnePassCLIv2, "_run", return_value=[0, "", ""])
|
||||
|
||||
|
@ -266,3 +272,34 @@ def test_signin(op_fixture, request):
|
|||
op._cli.signin()
|
||||
print(op._cli.version)
|
||||
op._cli._run.assert_called_once_with(['signin', '--raw'], command_input=b"master_pass")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("plugin", "connect_host", "connect_token"),
|
||||
[
|
||||
(plugin, connect_host, connect_token)
|
||||
for plugin in ("community.general.onepassword", "community.general.onepassword_raw")
|
||||
for (connect_host, connect_token) in
|
||||
(
|
||||
("http://localhost", None),
|
||||
(None, "foobar"),
|
||||
)
|
||||
]
|
||||
)
|
||||
def test_op_connect_partial_args(plugin, connect_host, connect_token):
|
||||
op_lookup = lookup_loader.get(plugin)
|
||||
with pytest.raises(AnsibleOptionsError):
|
||||
op_lookup.run("login", vault_name="test vault", connect_host=connect_host, connect_token=connect_token)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("kwargs"),
|
||||
(
|
||||
{"connect_host": "http://localhost", "connect_token": "foobar"},
|
||||
{"service_account_token": "foobar"},
|
||||
)
|
||||
)
|
||||
def test_opv1_unsupported_features(kwargs):
|
||||
op_cli = OnePassCLIv1(**kwargs)
|
||||
with pytest.raises(AnsibleLookupError):
|
||||
op_cli.full_signin()
|
||||
|
|
Loading…
Reference in a new issue