SingleConnect

Single Connect is a PAM product that enables enterprises to remove static passwords stored in applications by instead keeping passwords in a secure password vault. Single Connect provides a token-based authentication for 3rd party applications when accessing the password vault. This authentication process verifies the application identity and gives secure access to the password associated with that identity.

Authentication & Identity Management · Single Connect

Details

IDSingleConnect
ProviderVectra AI
CategoryAuthentication & Identity Management
From Version6.2.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Single Connect is a PAM product that enables enterprises to remove static passwords stored in applications by instead keeping passwords in a secure password vault. Single Connect provides a token-based authentication for 3rd party applications when accessing the password vault. This authentication process verifies the application identity and gives secure access to the password associated with that identity.

What does this pack do?

This integration provides the following commands for Shared Account Password Management (SAPM):

  • List all SAPM accounts (single-connect-sapm-account-list-all)
  • Show password for an SAPM account (single-connect-sapm-account-show-password)
  • List SAPM accounts of a specific device (single-connect-device-list-sapm-accounts)
  • Search SAPM accounts by secret name (single-connect-sapm-account-search-with-secret-name)

Configuration parameters

  • url — Your server URL (required)
  • credentials — Credentials (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (4)

  • single-connect-device-list-sapm-accounts

    List all the SAPM accounts on a specific device.

  • single-connect-sapm-account-list-all

    List all the accounts in the SAPM.

  • single-connect-sapm-account-search-with-secret-name

    List the accounts with given secretName.

  • single-connect-sapm-account-show-password

    Get the password for a specific SAPM account.

from CommonServerPython import *  # noqa # pylint: disable=unused-wildcard-import
from CommonServerUserPython import *  # noqa

import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """

""" CLIENT CLASS """


class Client(BaseClient):
    def __init__(self, base_url: str, username: str, password: str, use_ssl: bool, proxy: bool):
        super().__init__(base_url=base_url, verify=use_ssl, proxy=proxy)
        self._username = username
        self._password = password
        self._token = self._generate_token()
        self._headers = {"x-xsrf-token": self._token, "Content-Type": "application/json"}

    def _generate_token(self) -> str:
        body = {"username": self._username, "password": self._password}

        headers = {
            "Content-Type": "application/json",
        }
        try:
            response = self._http_request(
                "POST", "/aioc-rest-web/rest/login", json_data=body, headers=headers, resp_type="response"
            )
            return response.headers.get("XSRF-TOKEN")
        except Exception as e:
            if "401" in str(e):
                raise DemistoException("Authentication Error: Make sure username and password are correctly set")
            else:
                raise e

    def logout(self):
        self._http_request("POST", "/aioc-rest-web/rest/logout")

    def list_all_sapm_accounts(self):
        body = {"secretName": ""}
        response = self._http_request("POST", "/aioc-rest-web/rest/sc/sapm/searchSapmAccounts", json_data=body)
        return response

    def search_sapm_with_secret_name(self, secret_name: str):
        body = {"secretName": secret_name}
        return self._http_request("POST", "/aioc-rest-web/rest/sc/sapm/searchSapmAccounts", json_data=body)

    def show_password(self, password_expiration_in_minute=int, sapm_db_id=int, comment=str):
        body = {"passwordExpirationInMinute": password_expiration_in_minute, "sapmDbId": sapm_db_id, "comment": comment}
        return self._http_request("POST", "/aioc-rest-web/rest/sc/sapm/showPassword", json_data=body)

    def get_sapm_user_info(self, device_ip: str) -> dict[str, str]:
        body = {"deviceIp4Search": device_ip}
        return self._http_request("POST", "/aioc-rest-web/rest/sc/sapm/getSapmUserInfo", json_data=body)


""" COMMAND FUNCTIONS """


def test_module(client: Client) -> str:
    """Tests API connectivity and authentication

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.
    """

    try:
        response = client.list_all_sapm_accounts()
        try:
            search_results = response.get("searchResults")
        except Exception as e:
            raise Exception(f"Error in Single Connect API call: {e}")
        if search_results:
            if len(search_results) > 0:
                sapm_account = search_results[0]
                if not sapm_account.get("dbId"):
                    raise Exception("SAPM Accounts from Single Connect are missing mandatory fields: dbId")
            else:
                return "There are no SAPM Accounts but connection is ok"
            return "ok"
        else:
            raise Exception("Unexpected response format")
    except Exception as e:
        if "401" in str(e):
            raise DemistoException("Authentication Error: Make sure username and password are correctly set")
        else:
            raise e


def list_all_sapm_accounts_command(client: Client) -> CommandResults:
    response = client.list_all_sapm_accounts()
    try:
        result = response.get("searchResults")
    except Exception as e:
        raise Exception(f"Error in Single Connect API call: {e}")
    if result is not None:
        return CommandResults(outputs_prefix="SingleConnect.SapmAccount", outputs_key_field="dbId", outputs=result)
    else:
        raise Exception("Unexpected response format")


def search_sapm_with_secret_name_command(client: Client, secret_name: str) -> CommandResults:
    response = client.search_sapm_with_secret_name(secret_name)
    try:
        result = response.get("searchResults")
    except Exception as e:
        raise Exception(f"Error in Single Connect API call: {e}")

    if result is not None:
        return CommandResults(outputs_prefix="SingleConnect.SapmAccount", outputs_key_field="dbId", outputs=result)
    else:
        raise Exception("Unexpected response format")


def show_password_command(client: Client, password_expiration_in_minute=int, sapm_db_id=int, comment=str) -> CommandResults:
    response = client.show_password(password_expiration_in_minute, sapm_db_id, comment)
    if isinstance(response, str):
        raise Exception(f"Error in Single Connect API call: {response}")
    if isinstance(response, dict) and len(response) > 0:
        return CommandResults(outputs_prefix="SingleConnect.SapmAccount", outputs=response)
    else:
        raise Exception("Unexpected response format")


def get_sapm_user_info_command(client: Client, device_ip: str) -> CommandResults:
    response = client.get_sapm_user_info(device_ip)
    if isinstance(response, str):
        raise Exception(f"Error in Single Connect API call: {response}")
    if isinstance(response, list):
        return CommandResults(outputs_prefix="SingleConnect.SapmAccount", outputs_key_field="dbId", outputs=response)
    else:
        raise Exception("Unexpected response format")


""" MAIN FUNCTION """


def main() -> None:  # pragma: no cover
    """main function, parses params and runs command functions

    :return:
    :rtype:
    """
    params: dict[str, Any] = demisto.params()
    args: dict[str, Any] = demisto.args()
    command = demisto.command()
    username = params.get("credentials", {}).get("identifier")
    password = params.get("credentials", {}).get("password")
    base_url = params["url"]
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)
    demisto.debug(f"Command being called is {command}")
    try:
        client = Client(base_url=base_url, use_ssl=verify_certificate, username=username, password=password, proxy=proxy)

        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            return_results(test_module(client))

        elif command == "single-connect-sapm-account-list-all":
            return_results(list_all_sapm_accounts_command(client))

        elif command == "single-connect-sapm-account-show-password":
            return_results(show_password_command(client, **args))

        elif command == "single-connect-device-list-sapm-accounts":
            return_results(get_sapm_user_info_command(client, **args))

        elif command == "single-connect-sapm-account-search-with-secret-name":
            return_results(search_sapm_with_secret_name_command(client, **args))

    except Exception as e:
        return_error(f"Failed to execute {command} command. Error: {e!s}")
    finally:
        try:
            client.logout()
        except Exception as err:
            demisto.info(f"Single Connect error: {err!s}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()