Google Apigee

Apigee is Google Cloud's native API management platform that can be used to build, manage, and secure APIs — for any use case, environment, or scale. Apigee offers high performance API proxies to create a consistent, reliable interface for your backend services. The proxy layer gives you granular control over security, rate limiting, quotas, analytics, and more for all of your services. Apigee supports REST, gRPC, SOAP, and GraphQL, providing the flexibility to implement any API architectural style.

Cloud Services · Google Apigee Edge

Details

IDGoogle Apigee
ProviderGoogle
CategoryCloud Services
From Version8.4.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesXSIAM

README

Apigee is Google Cloud’s native API management platform that can be used to build, manage, and secure APIs — for any use case, environment, or scale. Apigee offers high performance API proxies to create a consistent, reliable interface for your backend services. The proxy layer gives you granular control over security, rate limiting, quotas, analytics, and more for all of your services. Apigee supports REST, gRPC, SOAP, and GraphQL, providing the flexibility to implement any API architectural style.
This integration was integrated and tested with version v1 of Google Apigee.

Configure Google Apigee in Cortex

Parameter Description Required
Server URL   True
Username (email address)   True
Password   True
Organization Name   True
Zone The zone name must be provided when accessing the Edge OAuth2 service from a SAML-enabled org. False
The maximum number of Audit Logs per fetch   False
Trust any certificate (not secure)   False
Use system proxy settings   False

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.

google-apigee-get-events


Gets logs from Google Apigee.

Base Command

google-apigee-get-events

Input

Argument Name Description Required
should_push_events Set this argument to True in order to create events, otherwise the command will only display them. Possible values are: true, false. Default is false. Required
limit The number of events to return. Optional
from_date Date from which to get events. Optional

Context Output

There is no context output for this command.

Configuration parameters

  • url — Server URL (required)
  • credentials — Username (email address) (required)
  • org_name — Organization Name (required)
  • zone — Zone
  • max_fetch — The maximum number of Audit Logs per fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • google-apigee-get-events

    Gets logs from Google Apigee.

import pytest

from GoogleApigeeEventCollector import (
    Client,
    fetch_events,
    get_events_command,
)


def mock_client(mocker):
    client = Client(
        base_url="https://test.com", verify=False, proxy=False, org_name="org", username="user", password="password", zone="zone"
    )
    mocker.patch.object(Client, "get_access_token", return_value={"access_token": "access_token"})
    mocker.patch.object(
        Client,
        "_http_request",
        return_value={"access_token": "access_token", "expires_in": 3600, "refresh_token": "refresh_token"},
    )
    return client


def test_get_events_command(requests_mock, mocker):
    """
    Tests google-apigee-get-events command function.
    Checks the output of the command function with the expected output.
    """
    client = mock_client(mocker)
    mock_response = {
        "auditRecord": [generate_mocked_event(13), generate_mocked_event(15)],
        "total_count": 2,
    }
    args = {
        "from_date": 3,
        "limit": 2,
    }
    requests_mock.get(f"https://test.com/v1/audits/organizations/{client.org_name}", json=mock_response)
    events, _ = get_events_command(client, args)

    assert len(events) == mock_response.get("total_count")
    assert events == mock_response.get("auditRecord")


def generate_mocked_event(event_time: int):
    return {"operation": "OPER", "requestUri": "some/uri", "responseCode": "200", "timeStamp": event_time, "user": "user"}


@pytest.mark.parametrize(
    "scenario, last_fetch, limit, events_amount, events_per_time, new_events_amount, last_event_time, events_size",
    [
        (
            "get all events between the timespan",  # scenario
            1,  # last_fetch
            7,  # limit
            0,  # events_amount
            [9, 9, 8, 7, 6, 5, 2],  # events_per_time,
            2,  # new_events_amount
            9,  # last_event_time
            7,  # events_size
        ),
        (
            "get all events between the timespan and limit > fetched_events",  # scenario
            1,  # last_fetch
            10,  # limit
            0,  # events_amount
            [9, 9, 8, 7, 6, 5, 2],  # events_per_time,
            0,  # new_events_amount
            9,  # last_event_time
            7,  # events_size
        ),
        (
            "testing starting from a timestamp where we already have existing events in the last fetch",  # scenario
            2,  # last_fetch
            3,  # limit
            3,  # events_amount
            [55, 8, 7, 2, 2, 2],  # events_per_time
            1,  # new_events_amount
            55,  # last_event_time
            3,  # events_size
        ),
        (
            "all events were already fetched",  # scenario
            9,  # last_fetch
            3,  # limit
            3,  # events_amount
            [9, 9, 9],  # events_per_time
            0,  # new_events_amount
            0,  # last_event_time
            0,  # events_size
        ),
        (
            "fetch more than limit",  # scenario
            1,  # last_fetch
            3,  # limit
            0,  # events_amount
            [9, 8, 7, 6, 5, 2],  # events_per_time
            1,  # new_events_amount
            6,  # last_event_time
            3,  # events_size
        ),
        (
            "fetch multiple events at the same time",  # scenario
            1,  # last_fetch
            5,  # limit
            0,  # events_amount
            [8, 8, 8, 8, 5, 2],  # events_per_time
            3,  # new_events_amount
            8,  # last_event_time
            5,  # events_size
        ),
        (
            "there is no logs",  # scenario
            1,  # last_fetch
            5,  # limit
            0,  # events_amount
            [],  # events_per_time
            0,  # new_events_amount
            0,  # last_event_time
            0,  # events_size
        ),
    ],
)
def test_fetch_events(
    mocker, scenario, last_fetch, limit, events_amount, events_per_time, new_events_amount, last_event_time, events_size
):
    """
    Tests fetch-events command function.
    Checks the output of the command function with the expected output.
    """

    def mock_get_logs(from_date, to_time):
        events = [generate_mocked_event(event_time) for event_time in events_per_time]
        return {
            "auditRecord": events,
            "total_count": len(events),
        }

    mocked_client = mocker.Mock()
    mocked_client.get_logs.side_effect = mock_get_logs
    mocked_client.max_fetch = limit

    last_run = {"last_fetch_events_amount": events_amount, "last_fetch_timestamp": last_fetch}
    next_run, events = fetch_events(client=mocked_client, last_run=last_run, limit=limit)

    assert len(events) == events_size
    assert (
        next_run.get("last_fetch_events_amount") == new_events_amount
    ), f"{scenario} - set last run does not match expected value"
    if events:
        assert events[0].get("timeStamp") == last_event_time
        assert events[-1].get("timeStamp") >= last_fetch


def test_test_module(requests_mock, mocker):
    """
    Tests test-module command function.
    Checks the output of the command function with the expected output.
    """
    from GoogleApigeeEventCollector import test_module

    client = mock_client(mocker)
    requests_mock.get(f"https://test.com/v1/audits/organizations/{client.org_name}", json={})
    res = test_module(client)

    assert res == "ok"


@pytest.mark.parametrize(
    "scenario, token_initiate_time, token_expiration_seconds, current_time, result",
    [
        (
            "valid token",  # scenario
            120,  # token_initiate_time
            400,  # token_expiration_seconds
            140,  # current_time
            True,  # result
        ),
        (
            "invalid token",  # scenario
            120,  # token_initiate_time
            100,  # token_expiration_seconds
            300,  # current_time
            False,  # result
        ),
    ],
)
def test_is_token_valid(mocker, scenario, token_initiate_time, token_expiration_seconds, current_time, result):
    is_token_valid = Client.is_token_valid(token_initiate_time, token_expiration_seconds, current_time)
    assert is_token_valid == result, f"{scenario} - does not match expected value"


def test_get_token_with_username(mocker):
    """
    Given:
        - A mock client configured with username and password credentials.
    When:
        - Calling `get_token_request` to retrieve an access token.
    Then:
        - Ensure the HTTP request is made with the correct method, URL, headers, and body.
        - Verify the returned access_token, expires_in, and refresh_token values.
    """
    client = mock_client(mocker)
    spy_http = mocker.spy(Client, "_http_request")
    access_token, expires_in, refresh_token = client.get_token_request()
    assert access_token == "access_token"
    assert expires_in == 3600
    assert refresh_token == "refresh_token"
    args = spy_http.call_args
    assert args[0] == ("POST",)
    assert args[1] == {
        "full_url": "https://zone.login.apigee.com/oauth/token",
        "url_suffix": "/oauth/token",
        "data": {"grant_type": "password", "username": "user", "password": "password"},
        "headers": {
            "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
            "Accept": "application/json;charset=utf-8",
            "Authorization": "Basic ZWRnZWNsaTplZGdlY2xpc2VjcmV0",
        },
    }


def test_get_token_with_valid_refresh_token(mocker):
    """
    Given:
        - A mock client configured to use a refresh token.
    When:
        - Calling `get_token_request` with a valid refresh token.
    Then:
        - Ensure the HTTP request is sent as a POST to the correct URL.
        - Confirm the payload includes the proper grant type and refresh token.
        - Verify the expected access_token, expires_in, and refresh_token are returned.
    """
    client = mock_client(mocker)
    spy_http = mocker.spy(Client, "_http_request")
    access_token, expires_in, refresh_token = client.get_token_request(refresh_token="refresh_token")
    assert access_token == "access_token"
    assert expires_in == 3600
    assert refresh_token == "refresh_token"

    args = spy_http.call_args
    assert args[0] == ("POST",)
    assert args[1] == {
        "full_url": "https://zone.login.apigee.com/oauth/token",
        "url_suffix": "/oauth/token",
        "data": {"grant_type": "refresh_token", "refresh_token": "refresh_token"},
        "headers": {
            "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
            "Accept": "application/json;charset=utf-8",
            "Authorization": "Basic ZWRnZWNsaTplZGdlY2xpc2VjcmV0",
        },
    }


def test_get_token_with_invalid_refresh_token(mocker):
    """
    Given:
        - A mock client configured to use a refresh token.
    When:
        - Calling `get_token_request` with an invalid refresh token that triggers an exception.
    Then:
        - The client should retry the request using the password grant type.
        - Ensure the first HTTP request uses the refresh token grant type with the correct payload.
        - Ensure the second HTTP request uses the password grant type with the correct credentials.
        - Verify that the final response includes the expected access_token, expires_in, and refresh_token.
    """
    client = mock_client(mocker)
    http_mock = mocker.patch.object(
        Client,
        "_http_request",
        side_effect=[
            Exception("Invalid refresh token"),
            {"access_token": "access_token", "expires_in": 3600, "refresh_token": "refresh_token"},
        ],
    )
    access_token, expires_in, refresh_token = client.get_token_request(refresh_token="refresh_token")

    assert http_mock.call_count == 2
    first_call = http_mock.call_args_list[0]
    second_call = http_mock.call_args_list[1]

    # Inspecting the first call
    assert first_call[0] == ("POST",)
    assert first_call[1]["data"]["grant_type"] == "refresh_token"
    assert first_call[1]["data"]["refresh_token"] == "refresh_token"

    # Inspecting the second call
    assert second_call[0] == ("POST",)
    assert second_call[1]["data"]["grant_type"] == "password"
    assert second_call[1]["data"]["username"] == "user"
    assert second_call[1]["data"]["password"] == "password"

    assert access_token == "access_token"
    assert expires_in == 3600
    assert refresh_token == "refresh_token"