Google Vertex AI

Fine-tuned to conduct natural conversation. Using Google Vertex Ai (PaLM API for Chat). The current integration of Google Vertex Ai is focusing only on the Generative AI model (PaLM) using the Chat prediction. Later, this plugin will be updated to include the following: - Model Creation - Model Fine Tuning - PaLM Text.

Data Enrichment & Threat Intelligence · Google Vertex AI

Details

IDGoogle Vertex AI
ProviderGoogle
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/googleapi-python3:1.0.0.115085
Supported ModulesAgentix XSIAM

README

Google Vertex AI

Integration Author: Sameh El-Hakim


Fine-tuned to conduct natural conversation. Using Google Vertex Ai (PaLM API for Chat)

The current integration of Google Vertex Ai is focusing only on the Generative AI model (PaLM) using the Chat prediction.

Later, this plugin will be updated to include the following:
PaLM for Text (Once the New API is released to public from Google will be modified to support quick integration)
Access to Model Garden through Playbooks
Model Development

Once the New API for (PaLM for Chat & Text) is released to the public from Google, then this integration will be modified to support quick integration. This integration is using an early version of Generative AI API from Google. So, you are expected to face some challenges.
***

The setup steps as following

  1. Create a new project on Google Cloud (Recommended instead of using existing project)
  2. Enable Vertex AI API
  3. Configure Consent Page
  4. Create OAuth Client ID
  5. Generate Authentication Code (OAuth Code)
  6. Setup XSOAR Instance
  7. Testing Command

Last Section will be Troubleshooting; the test button is not working with OAuth2 Method
***
If you have a knowledge of Google Cloud Administration, you can configure the project & API and skip directly to step 2.

Step 1: Create a new project on Google Cloud

In this step, you will need to have permission to create a new project in your GCP console

  1. Login to GCP Console:
    https://console.cloud.google.com/

  2. Click on Create Project

CreateProject

  1. Fill project Name: XSOAR_VertexAI or any name, then click on Create

projName1

  1. Select the new created project
  2. Go to marketplace

marketplacesearch

  1. Search & Select Vertex AI API

vertexAPIMarketplace

  1. Click Enable

enableVertexAIAPI

Step 2: Configure Consent Page

  1. Click on Configure Consent Screen

consentScreen

  1. Select Internal as User Type and Click on Create; It is recommended to limit the access to your project scope to Internal users in your organization as later as planned you can build your own Model and fine tune in a confidential environment that is shared publicly

userType

  1. Fill the App information (Fill only the mandatory fields as below, rest are optional) - Click on Save and Continue

appInfo

  1. Click Add or Remove Scopes; We will add Vertex AI API as part of the project scope; NOTE: Don’t add unnecessary scope as this might reveal other data in the project using the created credential

AppScope

  1. In current version of this integration, it is only require read only permission in the Scope; Then Click Update

readOnlyScope

After added, it will looks like this screenshot

finalAppLook

  1. Click Save and Continue; Now Step 3

Step 3: Create OAuth Client ID

  1. Go to APIs & Services > Credentials

credential

  1. Click Create Credentials

createCredential

  1. Fill your Credential Information as following

Application Type: Web application

Name: XSOAR-VertexAI

In Authorized redirect URIs: https://oproxy.demisto.ninja/authcode

This one will be easy as a user experience to generate the auth code; Please see Step 4

authCode

  1. Copy Client ID & Client secret, we will use them during XSOAR’s instance configurations; Then Click OK

credentialInfo

Step 4: Generate Authentication Code (OAuth Code)

In this step, we will use the created client ID & secret to generate OAuth Code so, the integration can generate access token for authentication & authorization to Google APIs. For more information about Tokens: please check the following URL from Google:

https://developers.google.com/identity/protocols/oauth2

https://cloud.google.com/docs/authentication/token-types

  1. There are two ways to generate the required URL, you can create an instance of the integration and add all information except for auth code as still we don’t have it

First get the project id by clicking on the project name from top left then it will looks as following:

projectID

  1. Fill the instance information on XSOAR as following:

instanceInfo

  1. In XSOAR’s CLI, execute the following command:

!google-vertex-ai-generate-auth-url

  1. Copy the generated authorization url to your browser and go to step 6

authorizationURL

  1. You can skip previous configuration and use the following URL after filling the required parameters

URL Format:

https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?scope=https://www.googleapis.com/auth/cloud-platform&access_type=offline&prompt=consent&response_type=code&state=state_parameter_passthrough_value&redirect_uri={REDIRECT_URI}&client_id={CLIENT_ID}

{REDIRECT_URI) replace it with: https://oproxy.demisto.ninja/authcode

{CLIENT_ID} replace it with: You Client ID that is generated in step 3

So, final URL should like that:

https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?scope=https://www.googleapis.com/auth/cloud-platform&access_type=offline&prompt=consent&response_type=code&state=state_parameter_passthrough_value&redirect_uri=https://oproxy.demisto.ninja/authcode&client_id=223432736531-aqebta31ip0t35vr07gldb4qj9egj2na.apps.googleusercontent.com

  1. Choose your account or Sign in

image20

  1. Click on Allow

allow

  1. It will redirect you to the REDIRECT_URI domain

https://oproxy.demisto.ninja/authcode

The beauty of using the OProxy is to make it easier for users to copy paste the code instead of using manual way from Browser Address bar in case of using localhost as a redirect uri.

For more information about OProxy from Palo Alto Networks; check the following link:

https://xsoar.pan.dev/docs/reference/articles/o-proxy

  1. Copy the auth code to your configured XSOAR instance; the final look for XSOAR Instance should look like Step 5

Step 5: Setup XSOAR Instance

This is the final look for how your XSOAR instance will looks like

instancePreview

Step 7: Testing (Instance Test button doesn’t work with OAuth2 method)

Now it is time to put the integration in test.

  1. Execute the following command:

!google-vertex-PaLM-chat prompt=”Any message”

commandOutput


Troubleshooting

In case of any failure it will be related to authentication code expired or reset somehow. In that case, you will need to repeat steps of generating a new auth code and adding it to XSOAR. BUT before that most important to reset the cache to the integration as following:

  1. In the instance, click reset integration cache
  2. Save & Exit (Important)

resetIntegrationCache

  1. Repeat from step 4 to 7 to generate a new authentication code and configure your instance then test

Configuration parameters

  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • ID — Client ID (required)
  • Secret — Client Secret (required)
  • Authentication_Code — Authentication Code (OAuth2) - View documentation to generate the Authentication Code
  • ProjectID — Project ID (required)

Commands (2)

  • google-vertex-PaLM-chat

    Send Text to Google Vertex Ai (PaLM for Chat) and receive a generative ai response

  • google-vertex-ai-generate-auth-url

    This command will generate the authentication url required to generate auth code

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

""" IMPORTS """


import json

import urllib3

# Disable insecure warnings
urllib3.disable_warnings()


""" GLOBAL Variables """


DISABLE_SSL = demisto.params().get("insecure", False)
PROXY = demisto.params().get("proxy")
PROMPT = demisto.params().get("prompt")
SERVICE_SCOPES = "https://www.googleapis.com/auth/cloud-platform"
REDIRECT_URI = "https://oproxy.demisto.ninja/authcode"
AUTH_URL = "https://accounts.google.com/o/oauth2/auth"
ACCESS_TOKEN_URL = "https://oauth2.googleapis.com/token"
PROJECT_ID = demisto.params().get("ProjectID")
URL = "https://us-central1-aiplatform.googleapis.com/v1/projects/"
AI_Model = "chat-bison:predict"
TOKEN = demisto.params().get("token")
CLIENT_ID = demisto.params().get("ID")
CLIENT_SECRET = demisto.params().get("Secret")
AUTH_CODE = demisto.params().get("Authentication_Code")
ERROR_MSG = (
    "ERROR: The authentication code has been reset"
    "Please reset integration cache for Vetex AI Instance"
    "in XSOAR and regenerate the 'Authorization code'"
)


""" CLIENT CLASS """


class Client(BaseClient):
    """
    Client class to interact with Google Vertex AI API
    """

    def __init__(self, token_str: str, base_url: str, proxy: bool, verify: bool):
        super().__init__(base_url=URL, proxy=PROXY, verify=verify)
        self.token_str = token_str
        self.base_url = base_url
        self.proxy = proxy
        self.headers = {"Authorization": f"Bearer {self.token_str}", "Content-Type": "application/json"}

    def PaLMModel(self, prompt: str):
        options = {"instances": [{"messages": [{"content": prompt}]}]}
        return self._http_request(
            method="POST",
            url_suffix=f"{PROJECT_ID}/locations/us-central1/publishers/google/models/{AI_Model}",
            json_data=options,
            headers=self.headers,
        )


""" MAIN FUNCTIONS """


def createAuthorizationURL():
    # The client ID and access scopes are required.
    partOne = f"{AUTH_URL}/oauthchooseaccount?scope={SERVICE_SCOPES}&access_type=offline&prompt=consent"
    partTwo = f"&response_type=code&state=state_parameter_passthrough_value&redirect_uri={REDIRECT_URI}&client_id={CLIENT_ID}"
    authorization_url = partOne + partTwo
    return authorization_url


def check_access_token_validation():
    """
    Access tokens expires in 1 hour, then using refresh_access_token function we will request for a new access token
    """

    demisto.debug("Start Token Validation")

    integration_context: dict = get_integration_context()
    access_token: str = integration_context.get("access_token", "")
    valid_until: int = integration_context.get("valid_until", int)
    time_now = epoch_seconds()

    if access_token and (time_now < valid_until):
        demisto.debug("Access Token still valid")
        return access_token
    elif access_token and (time_now > valid_until):
        demisto.debug("Access Token is expired, using refresh token")
        access_token = refresh_access_token()
        return access_token
    else:
        access_token = get_access_token()
        return access_token


def get_access_token():
    """
    Generate a new Access Token using ClientID, ClientSecret and configured Authentication Code
    """

    demisto.debug("Generate a new access token")

    integration_context: dict = get_integration_context()

    data: dict = {
        "code": AUTH_CODE,
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "redirect_uri": REDIRECT_URI,
        "grant_type": "authorization_code",
    }

    response: requests.Response = requests.post(ACCESS_TOKEN_URL, data=data, verify=DISABLE_SSL)

    if not response.ok:
        error = error_parser(response)
        raise ValueError(f"Failed to get access token [{response.status_code}] - {error}")

    response_json: dict = response.json()
    access_token = response_json.get("access_token", "")
    expires_in: int = response_json.get("expires_in", 3595)
    refresh_token = response_json.get("refresh_token", "")

    time_now: int = epoch_seconds()
    time_buffer = 5  # seconds by which to shorten the validity period
    if expires_in - time_buffer > 0:
        expires_in -= time_buffer
    integration_context["refresh_token"] = refresh_token
    integration_context["access_token"] = access_token
    integration_context["valid_until"] = time_now + expires_in
    set_integration_context(integration_context)

    return access_token


def refresh_access_token():
    """
    A refresh token might stop working for one of these reasons:
    The user has revoked your app's access. The refresh token has not been used for six months
    https://developers.google.com/identity/protocols/oauth2#:~:text=Refresh%20token%20expiration,
    -You%20must%20write&text=A%20refresh%20token%20might%20stop,been%20used%20for%20six%20months.
    """

    demisto.debug("Refresh Access token using refresh_token from integration_context")

    integration_context: dict = get_integration_context()
    refresh_token: str = integration_context.get("refresh_token", "")

    data: dict = {
        "refresh_token": refresh_token,
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "grant_type": "refresh_token",
    }

    response: requests.Response = requests.post(ACCESS_TOKEN_URL, data=data, verify=DISABLE_SSL)

    if not response.ok:
        error = error_parser(response)
        raise ValueError(f"Failed to get refresh token [{response.status_code}] - {error}")

    response_json: dict = response.json()
    access_token = response_json.get("access_token", "")
    expires_in: int = response_json.get("expires_in", 3595)

    time_now: int = epoch_seconds()
    time_buffer = 5  # seconds by which to shorten the validity period
    if expires_in - time_buffer > 0:
        expires_in -= time_buffer
    integration_context["refresh_token"] = refresh_token
    integration_context["access_token"] = access_token
    integration_context["valid_until"] = time_now + expires_in
    set_integration_context(integration_context)

    return access_token


def epoch_seconds(d: datetime = None) -> int:
    """
    Return the number of seconds for given date. If no date, return current.
    :param d: timestamp datetime object
    :return: timestamp in epoch
    """

    if not d:
        d = datetime.utcnow()
    return int((d - datetime.utcfromtimestamp(0)).total_seconds())


def resetIntegrationContext():
    """
    In case of error related to authentication or authorization, the cached context will be reseted
    """

    demisto.debug(ERROR_MSG)

    integration_context: dict = get_integration_context()
    integration_context["refresh_token"] = ""
    integration_context["access_token"] = ""
    integration_context["valid_until"] = ""
    set_integration_context(integration_context)
    return True


def error_parser(resp_err: requests.Response) -> str:
    """
    Parse Error
    """

    try:
        response: dict = resp_err.json()
        if "Unauthorized" in response.get("error_description", ""):
            resetIntegrationContext()
            raise ValueError(ERROR_MSG)
        elif "invalid authentication credentials" in response.get("error_description", ""):
            resetIntegrationContext()
            raise ValueError(ERROR_MSG)
        elif "Bad" in response.get("error_description", ""):
            resetIntegrationContext()
            raise ValueError(ERROR_MSG)
        else:
            error = response.get("error", {})
            err_str = (
                f"{error.get('code', '')}: {error.get('message', '')}"
                if isinstance(error, dict)
                else response.get("error_description", "")
            )
            if err_str:
                demisto.debug(err_str)
                return err_str
            # If no error message
            raise ValueError
    except ValueError:
        return resp_err.text


def test_module(client: Client):
    """
    This is the call made when pressing the integration test button.
    """

    promptText = "Hi, what is your name"
    status = ""
    try:
        response = client.PaLMModel(promptText)
        rep = json.dumps(response)
        repJSON = json.loads(rep)
        PaLMResp = repJSON.get("predictions", [])[0].get("candidates", [])[0].get("content", "")
        if PaLMResp:
            status = "ok"
            return status
        else:
            status = (
                "There is an error in communciating with Google Vertex AI API- Please regenerate the Authentication Code again"
            )
    except Exception as e:
        exception_text = str(e).lower()
        if "Bad Request" in exception_text or "invalid_grant" in exception_text:
            status = ERROR_MSG
            return status
        else:
            raise e
    return status


def send_prompts_PaLM_command(client: Client, prompt: str) -> CommandResults:
    """
    Send Text to Bard
    """

    PaLM_response = client.PaLMModel(prompt)

    return PaLM_output(PaLM_response)


def PaLM_output(response) -> CommandResults:
    """
    Convert response from ChatGPT to a human readable format in markdown table

    :return: CommandResults return output of ChatGPT response
    :rtype: ``CommandResults``
    """

    if response and isinstance(response, dict):
        rep = json.dumps(response)
        repJSON = json.loads(rep)
        PaLMResp = repJSON.get("predictions", [])[0].get("candidates", [])[0].get("content", "")
        context = [{"PaLM Model Response": PaLMResp}]

        markdown = tableToMarkdown(
            "Google Vertex AI API Response",
            context,
        )

        results = CommandResults(
            readable_output=markdown, outputs_prefix="GoogleVertexAIResponse", outputs_key_field="predictions", outputs=context
        )

        return results
    else:
        raise DemistoException("Error in results")


""" MAIN FUNCTION """


def main():
    """
    Main function, runs command functions
    """

    params = demisto.params()
    args = demisto.args()
    command = demisto.command()

    verify = not params.get("insecure", False)

    try:
        if command == "test-module":
            access_token = check_access_token_validation()
            client = Client(token_str=access_token, base_url=URL, verify=verify, proxy=PROXY)
            return_results(test_module(client))
        elif command == "google-vertex-PaLM-chat":
            access_token = check_access_token_validation()
            client = Client(token_str=access_token, base_url=URL, verify=verify, proxy=PROXY)
            return_results(send_prompts_PaLM_command(client, **args))
        elif command == "google-vertex-ai-generate-auth-url":
            return_results(createAuthorizationURL())
    except Exception as e:
        if "Quota exceeded for quota metric" in str(e):
            return_error("Quota for Google Vertex API exceeded")
        else:
            return_error(str(e))


# python2 uses __builtin__ python3 uses builtins
if __name__ == "__builtin__" or __name__ == "builtins":
    main()