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

IDOnePassword
Provider1Password Inc.
CategoryAnalytics & SIEM
From Version8.4.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesXSIAM

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:

  1. Sign in to your 1Password account and click Integrations in the sidebar.
  2. Under the Directory tab, choose (•••) Other and enter a descriptive name for the integration, such as ‘Cortex XSIAM’.
  3. Enter a name for the bearer token and choose when it will expire.
  4. Ensure the token has access to the event types listed above.
  5. Click Issue Token to generate a new bearer token.
  6. 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: user
2024-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: user
2024-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 settings
  • event_types — Types of events to fetch (required)
  • audit_events_limit — Maximum number of audit events per fetch
  • item_usage_actions_limit — Maximum number of item usage actions per fetch
  • sign_in_attempts_limit — Maximum number of sign-in attempts per fetch

Commands (1)

  • 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.

import json
from datetime import datetime

import dateparser
import pytest
from OnePassword import Client
from pytest_mock import MockerFixture
from requests_mock import Mocker as RequestsMock

BASE_URL = "http://example.com"
HEADERS = {"Authorization": "Bearer MY-TOKEN-123", "Content-Type": "application/json"}


def util_load_json(path: str):
    """Loads the contents of a JSON file with the given path.

    Args:
        path (str): Path to JSON file.

    Returns:
        Decoded JSON file contents.
    """
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


@pytest.fixture
def authenticated_client() -> Client:
    """Fixture to create a OnePassword.Client instance"""
    return Client(base_url=BASE_URL, verify=False, proxy=False, headers=HEADERS)


def mock_client_get_events(event_feature: str, body: dict):
    if body.get("start_time"):
        # First iteration (from_date filter used)
        # response = {'has_more': True, 'cursor': 'qwerty4567', ... }
        response = util_load_json(f"test_data/{event_feature}_response_1.json")
    else:
        # Second and final iteration (pagination cursor from first response used)
        # response = {'has_more': False, ... }
        response = util_load_json(f"test_data/{event_feature}_response_2.json")

    return response


def test_get_limit_param_for_event_type():
    """
    Given:
        - Integration instance Configuration params containing 'limit' values.

    When:
        - Calling get_limit_param_for_event_type.

    Assert:
        - Ensure the 'limit' value of the specified event type is as expected.
    """
    from OnePassword import get_limit_param_for_event_type

    expected_audit_events_limit = "500"
    expected_sign_in_attempts_limit = "4000"
    params = {
        "audit_events_limit": expected_audit_events_limit,
        "sign_in_attempts_limit": expected_sign_in_attempts_limit,
    }

    audit_events_limit = get_limit_param_for_event_type(params, event_type="audit events")
    sign_in_attempts_limit = get_limit_param_for_event_type(params, event_type="sign in attempts")

    assert audit_events_limit == int(expected_audit_events_limit)
    assert sign_in_attempts_limit == int(expected_sign_in_attempts_limit)


def test_add_fields_event():
    """
    Given:
        - A raw 1Password event of type 'audit event'.

    When:
        - Calling add_fields_to_event.

    Assert:
        - Ensure the '_time' and 'SOURCE_LOG_TYPE' fields are added and correctly set.
    """
    from OnePassword import EVENT_DATE_FORMAT, FILTER_DATE_FORMAT, add_fields_to_event, arg_to_datetime

    event_timestamp = "2024-12-02T11:54:19.710457472Z"

    event_type = "audit event"
    raw_event = {
        "uuid": "12345",
        "timestamp": event_timestamp,
        "action": "create",
        "object_type": "device",
    }
    add_fields_to_event(raw_event, event_type)

    assert raw_event["_time"] == arg_to_datetime(event_timestamp).strftime(EVENT_DATE_FORMAT)
    assert raw_event["timestamp_ms"] == arg_to_datetime(event_timestamp).strftime(FILTER_DATE_FORMAT)
    assert raw_event["SOURCE_LOG_TYPE"] == event_type.upper()


def test_create_get_events_request_body_invalid_inputs():
    """
    Given:
        - Missing pagination cursor and from date.

    When:
        - Calling create_get_events_request_body.

    Assert:
        - Ensure a ValueError is raised with the appropriate error message.
    """
    from OnePassword import create_get_events_request_body

    with pytest.raises(ValueError, match="Either a 'pagination_cursor' or a 'from_date' need to be specified."):
        create_get_events_request_body()


@pytest.mark.parametrize(
    "from_date, pagination_cursor, expected_request_body",
    [
        pytest.param(
            datetime(2024, 12, 2, 11, 50),
            None,
            {"limit": 1000, "start_time": "2024-12-02T11:50:00.000000Z"},
            id="Reset cursor (date filter)",
        ),
        pytest.param(
            None,
            "PAGE123",
            {"cursor": "PAGE123"},
            id="Pagination cursor",
        ),
    ],
)
def test_create_get_events_request_body_valid_inputs(
    from_date: datetime | None,
    pagination_cursor: str | None,
    expected_request_body: dict,
):
    """
    Given:
        - A from date or a pagination cursor.

    When:
        - Calling create_get_events_request_body.

    Assert:
        - Ensure the request body is as expected.
    """
    from OnePassword import create_get_events_request_body

    request_body = create_get_events_request_body(from_date=from_date, pagination_cursor=pagination_cursor)

    assert request_body == expected_request_body


def test_client_get_events(authenticated_client: Client, mocker: MockerFixture):
    """
    Given:
        - A OnePassword.Client instance with valid inputs to the get_events method.

    When:
        - Calling Client.get_events.

    Assert:
        - Ensure no exception is raised and the raw API response is as expected.
    """

    event_feature = "signinattempts"
    request_body = {"cursor": "12345"}

    client_http_request = mocker.patch.object(authenticated_client, "_http_request")

    authenticated_client.get_events(event_feature, request_body)

    client_http_request_kwargs = client_http_request.call_args.kwargs

    assert client_http_request_kwargs["method"] == "POST"
    assert client_http_request_kwargs["url_suffix"] == "/api/v2/signinattempts"
    assert client_http_request_kwargs["json_data"] == request_body
    assert client_http_request_kwargs["raise_on_status"] is True


def test_get_events_from_client_no_skip(authenticated_client: Client, mocker: MockerFixture):
    """
    Given:
        - A 1Password event type, from date, and the maximum number of events.

    When:
        - Calling get_events_from_client (which calls Client.get_events).

    Assert:
        - Ensure Client.get_events is called twice (because first response['has_more'] is True).
        - Ensure the number of events does not exceed the specified maximum and the events are as expected.
    """
    from OnePassword import get_events_from_client

    event_type = "audit events"
    from_date = datetime(2024, 12, 2, 11, 50)
    max_events = 3

    client_get_events = mocker.patch.object(authenticated_client, "get_events", side_effect=mock_client_get_events)

    events = get_events_from_client(authenticated_client, event_type=event_type, from_date=from_date, max_events=max_events)

    expected_events = util_load_json("test_data/auditevents_expected_events.json")

    assert client_get_events.call_count == 2
    assert events == expected_events


def test_get_events_from_client_skip_ids(authenticated_client: Client, mocker: MockerFixture):
    """
    Given:
        - A 1Password event type, from date, and the maximum number of events.

    When:
        - Calling get_events_from_client (which calls Client.get_events).

    Assert:
        - Ensure Client.get_events is called once (because response['has_more'] is False).
        - Ensure no events are returned (because the event ID in the response should be skipped).
    """
    from OnePassword import get_events_from_client

    event_type = "audit events"
    from_date = datetime(2024, 12, 2, 11, 50)
    max_events = 2

    response = util_load_json("test_data/auditevents_response_2.json")
    already_fetched_ids_to_skip = {"last event"}
    client_get_events = mocker.patch.object(authenticated_client, "get_events", return_value=response)

    events = get_events_from_client(
        authenticated_client,
        event_type=event_type,
        from_date=from_date,
        max_events=max_events,
        already_fetched_ids_to_skip=already_fetched_ids_to_skip,
    )

    assert client_get_events.call_count == 1
    assert events == []  # No new events because event ID in response has already been fetched and should be skipped


def test_push_events(mocker: MockerFixture):
    """
    Given:
        - A list of 1Password events of type 'audit events'.

    When:
        - Calling push_events.

    Assert:
        - Ensure send_events_to_xsiam is called once with the correct inputs.
    """
    from OnePassword import PRODUCT as EXPECTED_PRODUCT
    from OnePassword import VENDOR as EXPECTED_VENDOR
    from OnePassword import push_events

    send_events_to_xsiam = mocker.patch("OnePassword.send_events_to_xsiam")

    expected_events = util_load_json("test_data/auditevents_expected_events.json")
    push_events(expected_events)

    send_events_to_xsiam_kwargs = send_events_to_xsiam.call_args.kwargs

    assert send_events_to_xsiam.call_count == 1
    assert send_events_to_xsiam_kwargs["events"] == expected_events
    assert send_events_to_xsiam_kwargs["vendor"] == EXPECTED_VENDOR
    assert send_events_to_xsiam_kwargs["product"] == EXPECTED_PRODUCT


def test_set_next_run(mocker: MockerFixture):
    """
    Given:
        - A next run dictionary for 1Password event feature 'auditevents'.

    When:
        - Calling set_next_run.

    Assert:
        - Ensure demisto.setLastRun is called once with the correct inputs.
    """
    from OnePassword import set_next_run

    demisto_set_last_run = mocker.patch("OnePassword.demisto.setLastRun")

    next_run = {"auditevents": {"from_date": "2024-12-02T11:55:20.710457Z", "ids": ["last event"]}}
    set_next_run(next_run)

    assert demisto_set_last_run.call_count == 1
    assert demisto_set_last_run.call_args[0][0] == next_run


def test_fetch_events(authenticated_client: Client, mocker: MockerFixture):
    """
    Given:
        - A 1Password event type, first fetch date, and the maximum number of events per fetch.

    When:
        - Calling fetch_events (which calls get_events_from_client).

    Assert:
        - Ensure correct inputs to get_events_from_client.
        - Ensure correct fetch_events outputs (last_run, events).
    """
    from OnePassword import fetch_events

    # Inputs
    event_type = "audit events"
    from_date = "2024-12-02T11:54:11Z"

    # Expected outputs
    expected_type_next_run = {"from_date": "2024-12-02T11:55:20.710457Z", "ids": ["middle event", "last event"]}
    expected_events = util_load_json("test_data/auditevents_expected_events.json")

    get_events_from_client = mocker.patch("OnePassword.get_events_from_client", return_value=expected_events)
    type_next_run, events = fetch_events(
        authenticated_client,
        event_type=event_type,
        event_type_last_run={"from_date": from_date, "ids": []},
        event_type_max_results=1000,
    )

    get_events_from_client_kwargs = get_events_from_client.call_args.kwargs

    # Assert correct inputs
    assert get_events_from_client_kwargs["event_type"] == event_type
    assert get_events_from_client_kwargs["from_date"] == dateparser.parse(from_date)

    # Assert correct outputs
    assert type_next_run["from_date"] == expected_type_next_run["from_date"]
    assert sorted(type_next_run["ids"]) == sorted(expected_type_next_run["ids"])
    assert events == expected_events


def test_test_module_command(authenticated_client: Client, requests_mock: RequestsMock):
    """
    Given:
        - A list of 1Password event types and a OnePassword.Client instance.

    When:
        - Calling test_module_command (which calls Client.get_events).

    Assert:
        - Ensure client errors are gracefully handled and the correct error message appears.
    """
    from OnePassword import test_module_command, urljoin

    event_types = ["Sign in attempts"]

    event_url = urljoin(BASE_URL, "api/v2/signinattempts")
    mock_response = {"Error": {"Message": "Unauthorized"}}
    requests_mock.post(event_url, json=mock_response, status_code=401)

    result = test_module_command(authenticated_client, event_types)

    assert result == "Authorization Error: Make sure the API server URL and token are correctly set"


def test_get_events_command(authenticated_client: Client, mocker: MockerFixture):
    """
    Given:
        - A 1Password event type, from date, and the maximum number of events (limit).

    When:
        - Calling get_events_command (which calls get_events_from_client and tableToMarkdown).

    Assert:
        - Ensure correct inputs to tableToMarkdown.
        - Ensure correct get_events_command outputs.
    """
    from OnePassword import flattenTable, get_events_command

    expected_events = util_load_json("test_data/auditevents_expected_events.json")

    mocker.patch("OnePassword.get_events_from_client", return_value=expected_events)
    table_to_markdown = mocker.patch("OnePassword.tableToMarkdown")
    event_type = "audit events"
    args = {"event_type": event_type, "limit": "10", "from_date": "2024-12-02T11:55:00Z"}

    events, _ = get_events_command(authenticated_client, args)
    table_to_markdown_kwargs = table_to_markdown.call_args.kwargs

    assert table_to_markdown_kwargs["name"] == event_type.capitalize()
    assert table_to_markdown_kwargs["t"] == flattenTable(expected_events)

    assert events == expected_events