Repository: ansible-collections/community.general

Issue:
onepassword lookup with service account
### Summary

1password has new service accounts. via lookup should be possible to get secrets.

we can offer to contribute, but want to know, do you want a new lookup file for that, or should we try to expand: plugins/lookup/onepassword.py ?

### Issue Type

Feature Idea

### Component Name

lookup onepassword

### Additional Information

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)

```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/lookup/onepassword.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/lookup/onepassword.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @azenk @samdoran @scottsb
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I would also like this feature and can help with a contribution.
Support for service accounts should be added to the existing `onepassword` and `onepassword_raw` lookups. The `OP_SERVICE_ACCOUNT_TOKEN` needs to be added to the environment running `op` commands and should be a new parameter
Hey @samdoran added the service_account_token. Works everything fine. Please review and feel free to change or improve the PR. Thanks!
I was looking at using service accounts for my Ansible playbook since I need an initial sign in and don't want to use my master password with unrestricted read/write access to all vaults for security reasons, so I tried out @Domi-cc's fork directly instead of waiting for the 7.1.0 release by installing it via:
```
ansible-galaxy collection install git@github.com:Domi-cc/community.general.git,onepassword-lookup-add-service-accounts
```
And I can confirm that it works as expected. Had just to pass my generated token with the `service_account_token` parameter and could retrieve secrets from the corresponding vault.
This is indeed a much better way to do things than passing around the master password. I'm glad service account tokens are a thing now.

Resolving patch:
diff --git a/plugins/lookup/onepassword.py b/plugins/lookup/onepassword.py
--- a/plugins/lookup/onepassword.py
+++ b/plugins/lookup/onepassword.py
@@ -42,6 +42,12 @@
         description: The username used to sign in.
       secret_key:
         description: The secret key used when performing an initial sign in.
+      service_account_token:
+        description:
+          - The access key for a service account.
+          - Only works with 1Password CLI version 2 or later.
+        type: str
+        version_added: 7.1.0
       vault:
         description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
     notes:
@@ -113,12 +119,13 @@
 class OnePassCLIBase(with_metaclass(abc.ABCMeta, object)):
     bin = "op"
 
-    def __init__(self, subdomain=None, domain="1password.com", username=None, secret_key=None, master_password=None):
+    def __init__(self, subdomain=None, domain="1password.com", username=None, secret_key=None, master_password=None, service_account_token=None):
         self.subdomain = subdomain
         self.domain = domain
         self.username = username
         self.master_password = master_password
         self.secret_key = secret_key
+        self.service_account_token = service_account_token
 
         self._path = None
         self._version = None
@@ -295,6 +302,10 @@ def assert_logged_in(self):
         return not bool(rc)
 
     def full_signin(self):
+        if self.service_account_token:
+            raise AnsibleLookupError(
+                "1Password CLI version 1 does not support Service Accounts. Please use version 2 or later.")
+
         required_params = [
             "subdomain",
             "username",
@@ -472,6 +483,13 @@ def _parse_field(self, data_json, field_name, section_title=None):
         return ""
 
     def assert_logged_in(self):
+        if self.service_account_token:
+            args = ["whoami"]
+            environment_update = {"OP_SERVICE_ACCOUNT_TOKEN": self.service_account_token}
+            rc, out, err = self._run(args, environment_update=environment_update)
+
+            return not bool(rc)
+
         args = ["account", "list"]
         if self.subdomain:
             account = "{subdomain}.{domain}".format(subdomain=self.subdomain, domain=self.domain)
@@ -517,6 +535,13 @@ def get_raw(self, item_id, vault=None, token=None):
         args = ["item", "get", item_id, "--format", "json"]
         if vault is not None:
             args += ["--vault={0}".format(vault)]
+
+        if self.service_account_token:
+            if vault is None:
+                raise AnsibleLookupError("'vault' is required with 'service_account_token'")
+            environment_update = {"OP_SERVICE_ACCOUNT_TOKEN": self.service_account_token}
+            return self._run(args, environment_update=environment_update)
+
         if token is not None:
             args += [to_bytes("--session=") + token]
 
@@ -533,12 +558,14 @@ def signin(self):
 
 
 class OnePass(object):
-    def __init__(self, subdomain=None, domain="1password.com", username=None, secret_key=None, master_password=None):
+    def __init__(self, subdomain=None, domain="1password.com", username=None, secret_key=None, master_password=None,
+                 service_account_token=None):
         self.subdomain = subdomain
         self.domain = domain
         self.username = username
         self.secret_key = secret_key
         self.master_password = master_password
+        self.service_account_token = service_account_token
 
         self.logged_in = False
         self.token = None
@@ -551,7 +578,7 @@ def _get_cli_class(self):
         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)
+                    return cls(self.subdomain, self.domain, self.username, self.secret_key, self.master_password, self.service_account_token)
                 except TypeError as e:
                     raise AnsibleLookupError(e)
 
@@ -614,8 +641,9 @@ def run(self, terms, variables=None, **kwargs):
         username = self.get_option("username")
         secret_key = self.get_option("secret_key")
         master_password = self.get_option("master_password")
+        service_account_token = self.get_option("service_account_token")
 
-        op = OnePass(subdomain, domain, username, secret_key, master_password)
+        op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token)
         op.assert_logged_in()
 
         values = []
diff --git a/plugins/lookup/onepassword_raw.py b/plugins/lookup/onepassword_raw.py
--- a/plugins/lookup/onepassword_raw.py
+++ b/plugins/lookup/onepassword_raw.py
@@ -39,6 +39,12 @@
         description: The username used to sign in.
       secret_key:
         description: The secret key used when performing an initial sign in.
+      service_account_token:
+        description:
+          - The access key for a service account.
+          - Only works with 1Password CLI version 2 or later.
+        type: string
+        version_added: 7.1.0
       vault:
         description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
     notes:
@@ -89,8 +95,9 @@ def run(self, terms, variables=None, **kwargs):
         username = self.get_option("username")
         secret_key = self.get_option("secret_key")
         master_password = self.get_option("master_password")
+        service_account_token = self.get_option("service_account_token")
 
-        op = OnePass(subdomain, domain, username, secret_key, master_password)
+        op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token)
         op.assert_logged_in()
 
         values = []


Test patch: