AWSApiModule

AWS Client class, provides generic Infrastructure to all AWS integrations.

python · ApiModules

Source

import boto3
from botocore.config import Config
from CommonServerPython import *

from CommonServerUserPython import *

STS_ENDPOINTS = {
    "us-gov-west-1": "https://sts.us-gov-west-1.amazonaws.com",
    "us-gov-east-1": "https://sts.us-gov-east-1.amazonaws.com",
}  # See: https://docs.aws.amazon.com/general/latest/gr/sts.html


def validate_params(aws_default_region, aws_role_arn, aws_role_session_name, aws_access_key_id, aws_secret_access_key):
    """
    Validates that the provided parameters are compatible with the appropriate authentication method.
    """
    if not aws_default_region:
        raise DemistoException("You must specify AWS default region.")

    if bool(aws_access_key_id) != bool(aws_secret_access_key):
        raise DemistoException("You must provide Access Key id and Secret key id to configure the instance with credentials.")
    if bool(aws_role_arn) != bool(aws_role_session_name):
        raise DemistoException("Role session name is required when using role ARN.")


def extract_session_from_secret(secret_key, session_token):
    """
    Extract the session token from the secret_key field.
    """
    if secret_key and "@@@" in secret_key and not session_token:
        return secret_key.split("@@@")[0], secret_key.split("@@@")[1]
    else:
        return secret_key, session_token


class AWSClient:
    def __init__(
        self,
        aws_default_region,
        aws_role_arn,
        aws_role_session_name,
        aws_role_session_duration,
        aws_role_policy,
        aws_access_key_id,
        aws_secret_access_key,
        verify_certificate,
        timeout,
        retries,
        aws_session_token=None,
        sts_endpoint_url=None,
        endpoint_url=None,
        sts_region=None,
    ):
        self.sts_endpoint_url = sts_endpoint_url
        self.endpoint_url = endpoint_url
        self.aws_default_region = aws_default_region
        self.aws_role_arn = aws_role_arn
        self.aws_role_session_name = aws_role_session_name
        # handle cases where aws_role_session_duration can be also empty string
        self.aws_role_session_duration = aws_role_session_duration if aws_role_session_duration else None
        self.aws_role_policy = aws_role_policy
        self.aws_access_key_id = aws_access_key_id
        self.aws_secret_access_key, self.aws_session_token = extract_session_from_secret(aws_secret_access_key, aws_session_token)
        self.verify_certificate = verify_certificate
        self.sts_region = sts_region

        sts_regional_endpoint = demisto.params().get("sts_regional_endpoint") or None
        if sts_regional_endpoint:
            demisto.debug(f"Sets the environment variable AWS_STS_REGIONAL_ENDPOINTS={sts_regional_endpoint}")
            os.environ["AWS_STS_REGIONAL_ENDPOINTS"] = sts_regional_endpoint.lower()

        proxies = handle_proxy(proxy_param_name="proxy", checkbox_default_value=False)
        (read_timeout, connect_timeout) = AWSClient.get_timeout(timeout)
        if int(retries) > 10:
            retries = 10
        self.config = Config(
            connect_timeout=connect_timeout, read_timeout=read_timeout, retries={"max_attempts": int(retries)}, proxies=proxies
        )

    def update_config(self):
        command_config = {}
        retries = demisto.getArg("retries")  # Supports retries and timeout parameters on the command execution level
        if retries is not None:
            command_config["retries"] = {"max_attempts": int(retries)}
        timeout = demisto.getArg("timeout")
        if timeout is not None:
            (read_timeout, connect_timeout) = AWSClient.get_timeout(timeout)
            command_config["read_timeout"] = read_timeout
            command_config["connect_timeout"] = connect_timeout
        if retries or timeout:
            demisto.debug(f"Merging client config settings: {command_config}")
            self.config = self.config.merge(Config(**command_config))  # type: ignore[arg-type]

    def aws_session(
        self, service, region=None, role_arn=None, role_session_name=None, role_session_duration=None, role_policy=None
    ):
        kwargs = {}
        client = None

        self.update_config()

        if role_arn and role_session_name is not None:
            kwargs.update(
                {
                    "RoleArn": role_arn,
                    "RoleSessionName": role_session_name,
                }
            )
        elif self.aws_role_arn and self.aws_role_session_name is not None:
            kwargs.update(
                {
                    "RoleArn": self.aws_role_arn,
                    "RoleSessionName": self.aws_role_session_name,
                }
            )

        if role_session_duration is not None:
            kwargs.update({"DurationSeconds": int(role_session_duration)})
        elif self.aws_role_session_duration is not None:
            kwargs.update({"DurationSeconds": int(self.aws_role_session_duration)})

        if role_policy is not None:
            kwargs.update({"Policy": role_policy})
        elif self.aws_role_policy is not None:
            kwargs.update({"Policy": self.aws_role_policy})

        demisto.debug(f"{kwargs=}")
        self.sts_endpoint_url = self.sts_endpoint_url or STS_ENDPOINTS.get(region) or STS_ENDPOINTS.get(self.aws_default_region)

        calculated_sts_region = self.sts_region
        if not calculated_sts_region:
            calculated_sts_region = region if region else self.aws_default_region
        demisto.debug(
            f"Calculated sts region: {calculated_sts_region}, {self.sts_region=}, {region=}, {self.aws_default_region=}"
        )

        if kwargs and not self.aws_access_key_id:  # login with Role ARN
            if not self.aws_access_key_id:
                sts_client = boto3.client(
                    "sts",
                    config=self.config,
                    verify=self.verify_certificate,
                    region_name=calculated_sts_region,
                    endpoint_url=self.sts_endpoint_url,
                )
                sts_response = sts_client.assume_role(**kwargs)
                client = boto3.client(
                    service_name=service,
                    region_name=region if region else self.aws_default_region,
                    aws_access_key_id=sts_response["Credentials"]["AccessKeyId"],
                    aws_secret_access_key=sts_response["Credentials"]["SecretAccessKey"],
                    aws_session_token=sts_response["Credentials"]["SessionToken"],
                    verify=self.verify_certificate,
                    config=self.config,
                    endpoint_url=self.endpoint_url,
                )
        elif self.aws_access_key_id and (role_arn or self.aws_role_arn):  # login with Access Key ID and Role ARN
            sts_client = boto3.client(
                service_name="sts",
                region_name=calculated_sts_region,
                aws_access_key_id=self.aws_access_key_id,
                aws_secret_access_key=self.aws_secret_access_key,
                verify=self.verify_certificate,
                config=self.config,
                endpoint_url=self.sts_endpoint_url,
            )
            kwargs.update(
                {
                    "RoleArn": role_arn or self.aws_role_arn,
                    "RoleSessionName": role_session_name or self.aws_role_session_name,
                }
            )
            sts_response = sts_client.assume_role(**kwargs)
            client = boto3.client(
                service_name=service,
                region_name=region if region else self.aws_default_region,
                aws_access_key_id=sts_response["Credentials"]["AccessKeyId"],
                aws_secret_access_key=sts_response["Credentials"]["SecretAccessKey"],
                aws_session_token=sts_response["Credentials"]["SessionToken"],
                verify=self.verify_certificate,
                config=self.config,
                endpoint_url=self.endpoint_url,
            )
        elif self.aws_session_token and not self.aws_role_arn:  # login with session token
            client = boto3.client(
                service_name=service,
                region_name=region if region else self.aws_default_region,
                aws_access_key_id=self.aws_access_key_id,
                aws_secret_access_key=self.aws_secret_access_key,
                aws_session_token=self.aws_session_token,
                verify=self.verify_certificate,
                config=self.config,
                endpoint_url=self.endpoint_url,
            )
        elif self.aws_access_key_id and not self.aws_role_arn:  # login with access key id
            client = boto3.client(
                service_name=service,
                region_name=region if region else self.aws_default_region,
                aws_access_key_id=self.aws_access_key_id,
                aws_secret_access_key=self.aws_secret_access_key,
                verify=self.verify_certificate,
                config=self.config,
                endpoint_url=self.endpoint_url,
            )
        else:  # login with default permissions, permissions pulled from the ec2 metadata
            client = boto3.client(
                service_name=service, region_name=region if region else self.aws_default_region, endpoint_url=self.endpoint_url
            )

        return client

    @staticmethod
    def get_timeout(timeout):
        if not timeout:
            timeout = "60,10"  # default values
        try:
            if isinstance(timeout, int):
                read_timeout = timeout
                connect_timeout = 10

            else:
                timeout_vals = timeout.split(",")
                read_timeout = int(timeout_vals[0])
                # the default connect timeout is 10
                connect_timeout = 10 if len(timeout_vals) == 1 else int(timeout_vals[1])

        except ValueError:
            raise DemistoException(
                "You can specify just the read timeout (for example 60) or also the connect "
                "timeout followed after a comma (for example 60,10). If a connect timeout is not "
                "specified, a default of 10 second will be used."
            )
        return read_timeout, connect_timeout