OnePassword
Fetch events about actions performed by 1Password users within a specific account, access and modifications to items in shared vaults, and user sign-in attempts.
Analytics & SIEM · OnePassword
Details
| ID | OnePassword |
|---|---|
| Provider | 1Password Inc. |
| Category | Analytics & SIEM |
| From Version | 8.4.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | XSIAM |
README
1Password
Fetch events about actions performed by 1Password users within a specific account, access and modifications to items in shared vaults, and user sign-in attempts.
This integration was integrated and tested with V2 endpoints of the 1Password Events API.
Configure 1Password in Cortex
The integration can be configured to fetch three types of events from 1Password:
- Audit events - Information about actions performed by team members within a 1Password account. Events include details on a preformed action such as execution time and executing user, action type and any additional information about the activity.
- Item usage actions - Information about items in shared vaults that have been modified, accessed, or used. Events include the accessing user’s name and IP address, time of access, and the vault where the item is stored.
- Sign-in attempts - Information about sign-in attempts. Events include the name and IP address of the user who attempted to sign in to the account, when the attempt was made, and, for failed attempts, the cause of the failure.
All event timestamps, along with date and time configuration parameters and command arguments, are in the Coordinated Universal Time (UTC) timezone.
Every call to the 1Password Events API must be authorized with a bearer token. To issue a new bearer token:
- Sign in to your 1Password account and click Integrations in the sidebar.
- Under the Directory tab, choose (•••) Other and enter a descriptive name for the integration, such as ‘Cortex XSIAM’.
- Enter a name for the bearer token and choose when it will expire.
- Ensure the token has access to the event types listed above.
- Click Issue Token to generate a new bearer token.
- Save the token in a secure location and use it in configuring this integration instance.
| Parameter | Description | Required |
|---|---|---|
| Server URL | The API server URL depends on the domain where the account is hosted. Refer to the integration Help section for more details. | True |
| API Token | The bearer token used to authenticate with the 1Password Events API. This must include the required features (scopes) that correspond to the event types to be fetched. Refer to the integration Help section for more details. | True |
| Trust any certificate (not secure) | Allow connections without verifying the SSL certificate of the server. | False |
| Use system proxy settings | False | |
| Fetch Events | Whether to fetch events from 1Password. | False |
| Types of events to fetch | Types of events to fetch from 1Password. Possible values are: Audit events, Item usage actions, Sign in attempts. Default value is Audit events, Item usage actions, Sign in attempts. | True |
| Maximum number of audit events per fetch | If not specified, API default (100) will be used. | False |
| Maximum number of item usage actions per fetch | If not specified, API default (100) will be used. | False |
| Maximum number of sign-in attempts per fetch | If not specified, API default (100) will be used. | False |
Limitations
It is recommended to configure the integration instance so that the maximum number of fetched events does not exceed 100,000 per minute per event type. Otherwise, the 1Password Events API may raise rate limit errors (HTTP 429).
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
one-password-get-events
Fetch events from 1Password. This command is intended for development and debugging purposes and should be used with caution as it may create duplicate events.
Base Command
one-password-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| event_type | 1Password event type. Possible values are: Audit events, Item usage actions, Sign in attempts. | Required |
| limit | The maximum number of events to fetch for the given event type. Default is 1000. | Optional |
| from_date | The date from which to get events. If not specified, events from the last minute will be fetched. | Optional |
| should_push_events | Set this argument to True in order to push events to Cortex XSIAM, otherwise the command will only display them. Possible values are: True, False. Default is False. | Required |
Command Example
!one-password-get-events event_type="Sign in attempts" limit=2 from_date="2024-12-12T12:00:00.000Z" should_push_events=False
Context Output
There is no context output for this command.
Human Readable Output
Sign in attempts
account_uuid category client country location session_uuid target_user timestamp type uuid H9T8ZQ6P4F3JY1VK7D2N5L7XQ2W success app_name: 1Password Browser Extension
app_version: 81055002
…UK country: UK
region: Scotland
city: Glasgow
…YB7RPKX9V6N2DAG3T0ZQ8YHWY4C uuid: LJ5T8WK9U8FQGZ2D1Q4V9RLP3S
name: Jenny Bee
email: userB@example.com
type: user2024-12-13T19:52:14.658476952Z credentials_ok NAFGMYS3LZBCZLX2MVRSWNXIHI B7N3W5E9Y6JHQ2V1KZ8M4P0QX5T success app_name: 1Password for Web
app_version: 1895
…IL country: IL
region: Gush Dan
city: Tel Aviv
…C4XUJWF8N2Y1K9V3WZ7M5E6T0H uuid: MNY8UJ6PZG5BVW9QH3A1K2X3CK
name: John Doe
email: userA@example.com
type: user2024-12-16T13:27:33.375466135Z credentials_ok QXZTBRJULQX5ZJWKRFG8YTP8EX
Configuration parameters
url— Server URL (required)credentials— (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsevent_types— Types of events to fetch (required)audit_events_limit— Maximum number of audit events per fetchitem_usage_actions_limit— Maximum number of item usage actions per fetchsign_in_attempts_limit— Maximum number of sign-in attempts per fetch
Commands (1)
-
one-password-get-eventsFetch events from 1Password. This command is intended for development and debugging purposes and should be used with caution as it may create duplicate events.
from datetime import UTC, datetime, timedelta from http import HTTPStatus from operator import itemgetter import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 from requests.structures import CaseInsensitiveDict from CommonServerUserPython import * # noqa """ CONSTANTS """ EVENT_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # For XSIAM events - second precision FILTER_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" # For 1Password date filter - microsecond precision VENDOR = "1Password" PRODUCT = "1Password" # Default results per page (optimal value) # > 1000 causes HTTP 400 [Bad Request] # < 1000 increases the number of requests and may eventually trigger HTTP 429 [Rate Limit] DEFAULT_RESULTS_PER_PAGE = 1000 DEFAULT_MAX_EVENTS_PER_FETCH = 100 # Consistent with API default DEFAULT_FETCH_FROM_DATE = datetime.now(tz=UTC) - timedelta(minutes=1) # 1 minute ago in UTC timezone EVENT_TYPE_FEATURE = CaseInsensitiveDict( { # Display name: API Feature and endpoint name "Item usage actions": "itemusages", "Audit events": "auditevents", "Sign in attempts": "signinattempts", } ) EVENT_TYPE_LIMIT_PARAM = CaseInsensitiveDict( { # Display name: Max events per fetch (limit) param name "Item usage actions": "item_usage_actions_limit", "Audit events": "audit_events_limit", "Sign in attempts": "sign_in_attempts_limit", } ) """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the 1Password Events API""" def get_events(self, event_feature: str, body: dict[str, Any]) -> dict[str, Any]: """Gets events from 1Password based on the specified event type Args: feature (str): 1Password event feature (e.g. 'itemusages', 'signinattempts'). body (dict): The request body containing either a Reset Cursor (date filter) or a Pagination Cursor. Raises: DemistoException: If API responds with an HTTP error status code. Returns: dict: The response JSON from the event endpoint. """ demisto.debug(f"Requesting events of feature: {event_feature} using request body: {body}") return self._http_request(method="POST", url_suffix=f"/api/v2/{event_feature}", json_data=body, raise_on_status=True) """ HELPER FUNCTIONS """ def get_limit_param_for_event_type(params: dict[str, str], event_type: str) -> int: """Gets the limit parameter for a given event type. The limit represents the maximum number of events per fetch. Args: params (dict): The instance configuration parameters. event_type (str): Type of 1Password event (e.g. 'Item usage actions', 'Sign in attempts'). Returns: int: The maximum number of events per fetch. """ param_name = EVENT_TYPE_LIMIT_PARAM[event_type] limit = arg_to_number(params.get(param_name)) or DEFAULT_MAX_EVENTS_PER_FETCH demisto.debug(f"Maximum number of events per fetch for {event_type} is set to {limit}.") return limit def create_get_events_request_body( from_date: datetime | None = None, results_per_page: int = DEFAULT_RESULTS_PER_PAGE, pagination_cursor: str | None = None, ) -> dict[str, Any]: """Creates the request body for the `Client.get_events` method. Args: event_type (str): Type of 1Password event (e.g. 'Item usage actions', 'Sign in attempts'). from_date (datetime | None): Optional datetime from which to get events. results_per_page (int): The maximum number of records in response (Recommended to use default value). Raises: ValueError: If missing cursor and from date. Returns: dict[str, Any]: The request body. """ if pagination_cursor: return {"cursor": pagination_cursor} if from_date: formatted_from_date: str = from_date.strftime(FILTER_DATE_FORMAT) return {"limit": results_per_page, "start_time": formatted_from_date} raise ValueError("Either a 'pagination_cursor' or a 'from_date' need to be specified.") def add_fields_to_event(event: dict[str, Any], event_type: str): """Sets the '_time', 'SOURCE_LOG_TYPE', and 'timestamp_ms' fields in the event dictionary. Args: event (dict): Event dictionary with the new fields. event_type (str): Type of 1Password event (e.g. 'Item usage actions', 'Sign in attempts'). """ event_time = arg_to_datetime(event["timestamp"], required=True) # Required by XSIAM event["SOURCE_LOG_TYPE"] = event_type.upper() event["_time"] = event_time.strftime(EVENT_DATE_FORMAT) # type: ignore[union-attr] # Matches precision of date filter - ensures correct and accurate list of already fetched IDs event["timestamp_ms"] = event_time.strftime(FILTER_DATE_FORMAT) # type: ignore[union-attr] def get_events_from_client( client: Client, event_type: str, from_date: datetime, max_events: int, already_fetched_ids_to_skip: set[str] | None = None ) -> list[dict]: """Gets events of the specified type based on the `from_date` filter and `max_events` argument using cursor-based pagination. The first API call in each fetch run uses a "Reset Cursor" (date filter). Subsequent API calls in the same fetch run use "Pagination / Continuing Cursor". This ensures that even if we fetch part of the records on a given page and stop in the middle because we reached `max_events`, we can continue exactly where we stopped in the next fetch run via the Reset Cursor. Args: client (Client): 1Password Events API client. event_type (str): Type of 1Password event (e.g. 'Item usage actions', 'Sign in attempts'). from_date (datetime): Datetime from which to get events. max_events (int): Maximum number of events to fetch. already_fetched_ids_to_skip (set | None): Optional set of already-fetched event UUIDs that should be skipped. Raises: ValueError: If invalid event type or request body. DemistoException: If API request failed. Returns: list[dict]: List of events. """ event_feature = EVENT_TYPE_FEATURE.get(event_type) if not event_feature: raise ValueError(f"Invalid or unsupported {VENDOR} event type: {event_type}.") events: list[dict] = [] already_fetched_ids_to_skip = already_fetched_ids_to_skip or set() # Reset Cursor - First call to the paginated API needs to to include a date filter has_more_events = True request_body = create_get_events_request_body(from_date=from_date) while has_more_events: response_events: list[dict] = [] response_skipped_ids: set[str] = set() response = client.get_events(event_feature, body=request_body) pagination_cursor = response["cursor"] for event in response["items"]: event_id = event["uuid"] if event_id in already_fetched_ids_to_skip: response_skipped_ids.add(event_id) demisto.debug(f"Skipped duplicate event with ID: {event_id}") continue if len(events) == max_events: demisto.debug(f"Reached maximum number of events {max_events}. Last event ID: {event_id}") break add_fields_to_event(event, event_type) response_events.append(event) already_fetched_ids_to_skip.add(event_id) demisto.debug( f'Response has {len(response["items"])} events of type {event_type}. Saved {len(response_events)} events. ' f'Skipped {len(response_skipped_ids)} duplicates: ({", ".join(response_skipped_ids)}). ' f'Got pagination cursor: {pagination_cursor}. Used request body: {request_body}.' ) events.extend(response_events) # Pagination / Continuing cursor - Followup API calls need to the unique page ID (if any more events) has_more_events = False if len(events) == max_events else response["has_more"] request_body = create_get_events_request_body(pagination_cursor=pagination_cursor) return events def push_events(events: list[dict]) -> None: """Sends events to the relevant `VENDOR` and `PRODUCT` dataset in XSIAM. Args: events (list): List of event dictionaries. """ demisto.debug(f"Starting to send {len(events)} events to XSIAM.") send_events_to_xsiam(events=events, vendor=VENDOR, product=PRODUCT) def set_next_run(next_run: dict[str, Any]) -> None: """Sets next run for all event features. Should be called after events are successfully sent to XSIAM. Args: next_run (dict): Next run dictionary containing `from_date` in `FILTER_DATE_FORMAT` and `ids` list per event feature. Example: >>> set_next_run({"auditevents": {"from_date": "2024-12-02T11:54:19.710457Z", "ids": []}, "itemusages": ...}) """ demisto.debug(f"Setting next run to {next_run}.") demisto.setLastRun(next_run) """ COMMAND FUNCTIONS """ def fetch_events( client: Client, event_type: str, event_type_last_run: dict[str, Any], event_type_max_results: int, ) -> tuple[dict, list]: """Fetches new events via 1Password Events API client based on a specified event type. Args: client (Client): 1Password Events API client. event_type (str): Type of 1Password event (e.g. 'Item usage actions', 'Sign in attempts'). event_type_last_run (dict): Dictionary of the event type last run (if exists) with optional 'from_date' and 'ids' list. event_type_max_results (int): Maximum number of events of the event type to fetch. Returns: tuple[dict, list]: Dictionary of the next run of the event type with 'from_date' and 'ids' list, list of fetched events. """ last_run_ids_to_skip = set(event_type_last_run.get("ids") or []) from_date = arg_to_datetime(event_type_last_run.get("from_date")) or DEFAULT_FETCH_FROM_DATE demisto.debug(f"Fetching events of type: {event_type} from date: {from_date.strftime(FILTER_DATE_FORMAT)}") event_type_events = get_events_from_client( client=client, event_type=event_type, from_date=from_date, max_events=event_type_max_results, already_fetched_ids_to_skip=last_run_ids_to_skip, ) if event_type_events: # Use event 'timestamp_ms' since it is consistent with FILTER_DATE_FORMAT last_event_time = max(event_type_events, key=itemgetter("timestamp_ms"))["timestamp_ms"] next_run_ids_to_skip = {event["uuid"] for event in event_type_events if event["timestamp_ms"] == last_event_time} event_type_next_run = {"from_date": last_event_time, "ids": list(next_run_ids_to_skip)} else: last_event_time = None event_type_next_run = {"from_date": from_date.strftime(FILTER_DATE_FORMAT), "ids": list(last_run_ids_to_skip)} demisto.debug( f"Fetched {len(event_type_events)} events of type: {event_type} out of a maximum of {event_type_max_results}. " f"Last event time: {last_event_time}." ) return event_type_next_run, event_type_events def test_module_command(client: Client, event_types: list[str]) -> str: """Tests connectivity and authentication with 1Password Events API and verifies that the token has access to the configured event types. Args: client (Client): 1Password Events API client. event_types (list): List of event types from the integration configuration params. Returns: str: 'ok' if test passed, anything else will fail the test. Raises: DemistoException: If API call responds with an unhandled HTTP error status code or token does not have access to the configured event types. """ for event_type in event_types: try: get_events_from_client(client, event_type=event_type, from_date=DEFAULT_FETCH_FROM_DATE, max_events=1) except DemistoException as e: error_status_code = e.res.status_code if isinstance(e.res, requests.Response) else None if error_status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN): return "Authorization Error: Make sure the API server URL and token are correctly set" if error_status_code == HTTPStatus.NOT_FOUND: return "Endpoint Not Found: Make sure the API server URL is correctly set" # Some other unknown / unexpected error raise return "ok" def get_events_command(client: Client, args: dict[str, str]) -> tuple[list[dict], CommandResults]: """Implements `one-password-get-events` command, which returns a markdown table of events to the war room. Args: client (Client): 1Password Events API client. args (dict): The '1password-get-events' command arguments. Returns: tuple[list[dict], CommandResults]: List of events and CommandResults with human readable output. """ event_type = args["event_type"] limit = arg_to_number(args.get("limit")) or DEFAULT_MAX_EVENTS_PER_FETCH from_date = arg_to_datetime(args.get("from_date")) or DEFAULT_FETCH_FROM_DATE events = get_events_from_client(client, event_type=event_type, from_date=from_date, max_events=limit) human_readable = tableToMarkdown(name=event_type.capitalize(), t=flattenTable(events)) return events, CommandResults(readable_output=human_readable) """ MAIN FUNCTION """ def main() -> None: # pragma: no cover command = demisto.command() params = demisto.params() args = demisto.args() # required base_url: str = params["url"] token: str = params.get("credentials", {}).get("password", "") event_types: list[str] = argToList(params["event_types"], transform=lambda event_type: event_type.strip()) # optional verify_certificate: bool = not params.get("insecure", False) proxy: bool = params.get("proxy", False) demisto.debug(f"Command being called is {command!r}") try: client = Client( base_url=base_url, verify=verify_certificate, headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, proxy=proxy, ) if command == "test-module": result = test_module_command(client, event_types) return_results(result) elif command == "one-password-get-events": should_push_events = argToBoolean(args.pop("should_push_events")) events, results = get_events_command(client, args) return_results(results) if should_push_events: push_events(events) elif command == "fetch-events": all_events: list[dict] = [] last_run = demisto.getLastRun() next_run: dict[str, Any] = {} for event_type in event_types: event_type_key = EVENT_TYPE_FEATURE[event_type] event_type_last_run: dict = last_run.get(event_type_key, {}) event_type_max_results: int = get_limit_param_for_event_type(params, event_type) event_type_next_run, event_type_events = fetch_events( client=client, event_type=event_type, event_type_last_run=event_type_last_run, event_type_max_results=event_type_max_results, ) all_events.extend(event_type_events) next_run[event_type_key] = event_type_next_run push_events(all_events) set_next_run(next_run) else: raise NotImplementedError(f"Unknown command {command!r}") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command!r} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()