def set_iam_properties(info: RedshiftProperty) -> RedshiftProperty:
        """
        Helper function to handle connection properties and ensure required parameters are specified.
        Parameters
        """
        # set properties present for both IAM, Native authentication
        IamHelper.set_auth_properties(info)

        if info.is_serverless_host and info.iam:
            raise ProgrammingError("This feature is not yet available")
            # if Version(pkg_resources.get_distribution("boto3").version) <= Version("1.20.22"):
            #     raise pkg_resources.VersionConflict(
            #         "boto3 >= XXX required for authentication with Amazon Redshift serverless. "
            #         "Please upgrade the installed version of boto3 to use this functionality."
            #     )

        if info.is_serverless_host:
            info.set_account_id_from_host()
            info.set_region_from_host()

        if info.iam is True:
            if info.cluster_identifier is None and not info.is_serverless_host:
                raise InterfaceError(
                    "Invalid connection property setting. cluster_identifier must be provided when IAM is enabled"
                )
            IamHelper.set_iam_credentials(info)
        # Check for Browser based OAuth Native authentication
        NativeAuthPluginHelper.set_native_auth_plugin_properties(info)
        return info
예제 #2
0
def _make_aws_credentials_obj_with_profile() -> AWSCredentialsProvider:
    cred_provider: AWSCredentialsProvider = AWSCredentialsProvider()
    rp: RedshiftProperty = RedshiftProperty()
    profile_name: str = "myProfile"

    rp.profile = profile_name

    cred_provider.add_parameter(rp)
    return cred_provider
예제 #3
0
def set_cluster_credentials(cred_provider: SamlCredentialsProvider,
                            info: RedshiftProperty) -> None:
    import boto3  # type: ignore
    import botocore  # type: ignore

    try:
        credentials: CredentialsHolder = cred_provider.get_credentials()
        client = boto3.client(
            "redshift",
            region_name=info.region,
            aws_access_key_id=credentials.get_aws_access_key_id(),
            aws_secret_access_key=credentials.get_aws_secret_key(),
            aws_session_token=credentials.get_session_token(),
        )
        info.host, info.port = client.describe_clusters(
            ClusterIdentifier=info.cluster_identifier
        )["Clusters"][0]["Endpoint"].values()

        cred: dict = client.get_cluster_credentials(
            DbUser=info.db_user,
            DbName=info.db_name,
            DbGroups=info.db_groups,
            ClusterIdentifier=info.cluster_identifier,
            AutoCreate=info.auto_create,
        )
        info.user_name = cred["DbUser"]
        info.password = cred["DbPassword"]
    except botocore.exceptions.ClientError as e:
        _logger.error("ClientError: %s", e)
        raise e
    except client.exceptions.ClusterNotFoundFault as e:
        _logger.error("ClusterNotFoundFault: %s", e)
        raise e
    except client.exceptions.UnsupportedOperationFault as e:
        _logger.error("UnsupportedOperationFault: %s", e)
        raise e
    except botocore.exceptions.EndpointConnectionError as e:
        _logger.error("EndpointConnectionError: %s", e)
        raise e
    except Exception as e:
        _logger.error("other Exception: %s", e)
        raise e
    def get_credentials_cache_key(
        info: RedshiftProperty,
        cred_provider: typing.Union[SamlCredentialsProvider,
                                    AWSCredentialsProvider]):
        db_groups: str = ""

        if len(info.db_groups) > 0:
            info.put("db_groups", sorted(info.db_groups))
            db_groups = ",".join(info.db_groups)

        cred_key: str = ""

        if cred_provider:
            cred_key = str(cred_provider.get_cache_key())

        return ";".join(
            filter(
                None,
                (
                    cred_key,
                    typing.cast(
                        str, info.db_user if info.db_user else info.user_name),
                    info.db_name,
                    db_groups,
                    typing.cast(
                        str, info.account_id if info.is_serverless_host else
                        info.cluster_identifier),
                    str(info.auto_create),
                    str(info.duration),
                    # v2 api parameters
                    info.preferred_role,
                    info.web_identity_token,
                    info.role_arn,
                    info.role_session_name,
                    # providers
                    info.profile,
                    info.access_key_id,
                    info.secret_access_key,
                    info.session_token,
                ),
            ))
예제 #5
0
def make_valid_azure_oauth2_provider(
) -> typing.Tuple[BrowserAzureOAuth2CredentialsProvider, RedshiftProperty]:
    rp: RedshiftProperty = RedshiftProperty()
    rp.idp_tenant = "my_idp_tenant"
    rp.client_id = "my_client_id"
    rp.scope = "my_scope"
    rp.idp_response_timeout = 900
    rp.listen_port = 1099
    cp: BrowserAzureOAuth2CredentialsProvider = BrowserAzureOAuth2CredentialsProvider(
    )
    cp.add_parameter(rp)
    return cp, rp
예제 #6
0
    def read_auth_profile(
        auth_profile: str,
        iam_access_key_id: str,
        iam_secret_key: str,
        iam_session_token: typing.Optional[str],
        info: RedshiftProperty,
    ) -> RedshiftProperty:
        import json

        import boto3
        from botocore.exceptions import ClientError

        # 1st phase - authenticate with boto3 client for Amazon Redshift via IAM
        # credentials provided by end user
        creds: typing.Dict[str, str] = {
            "aws_access_key_id": iam_access_key_id,
            "aws_secret_access_key": iam_secret_key,
            "region_name": typing.cast(str, info.region),
        }

        for opt_key, opt_val in (
            ("aws_session_token", iam_session_token),
            ("endpoint_url", info.endpoint_url),
        ):
            if opt_val is not None and opt_val != "":
                creds[opt_key] = opt_val

        try:
            _logger.debug("Initial authentication with boto3...")
            client = boto3.client(service_name="redshift", **creds)
            _logger.debug("Requesting authentication profiles")
            # 2nd phase - request Amazon Redshift authentication profiles and record contents for retrieving
            # temporary credentials for the Amazon Redshift cluster specified by end user
            response = client.describe_authentication_profiles(
                AuthenticationProfileName=auth_profile)
        except ClientError:
            raise InterfaceError(
                "Unable to retrieve contents of Redshift authentication profile from server"
            )

        _logger.debug("Received {} authentication profiles".format(
            len(response["AuthenticationProfiles"])))
        # the first matching authentication profile will be used
        profile_content: typing.Union[str] = response[
            "AuthenticationProfiles"][0]["AuthenticationProfileContent"]

        try:
            profile_content_dict: typing.Dict = json.loads(profile_content)
            return RedshiftProperty(**profile_content_dict)
        except ValueError:
            raise ProgrammingError(
                "Unable to decode the JSON content of the Redshift authentication profile: {}"
                .format(auth_profile))
예제 #7
0
def _make_aws_credentials_obj_with_credentials() -> AWSCredentialsProvider:
    cred_provider: AWSCredentialsProvider = AWSCredentialsProvider()
    rp: RedshiftProperty = RedshiftProperty()
    access_key_id: str = "my_access"
    secret_key: str = "my_secret"
    session_token: str = "my_session"

    rp.access_key_id = access_key_id
    rp.secret_access_key = secret_key
    rp.session_token = session_token

    cred_provider.add_parameter(rp)
    return cred_provider
예제 #8
0
    def get_credentials_cache_key(info: RedshiftProperty):
        db_groups: str = ""

        if len(info.db_groups) > 0:
            info.db_groups = sorted(info.db_groups)
            db_groups = ",".join(info.db_groups)

        return ";".join((
            typing.cast(str, info.db_user),
            info.db_name,
            db_groups,
            typing.cast(str, info.cluster_identifier),
            str(info.auto_create),
        ))
def set_iam_credentials(info: RedshiftProperty) -> None:
    provider: typing.Optional[SamlCredentialsProvider] = None
    # case insensitive comparison
    if info.credentials_provider is None:
        return
    elif info.credentials_provider.lower() == "OktaCredentialsProvider".lower(
    ):
        provider = OktaCredentialsProvider()
        provider.add_parameter(info)
    elif info.credentials_provider.lower() == "AzureCredentialsProvider".lower(
    ):
        provider = AzureCredentialsProvider()
        provider.add_parameter(info)
    elif info.credentials_provider.lower(
    ) == "BrowserAzureCredentialsProvider".lower():
        provider = BrowserAzureCredentialsProvider()
        provider.add_parameter(info)
    elif info.credentials_provider.lower() == "PingCredentialsProvider".lower(
    ):
        provider = PingCredentialsProvider()
        provider.add_parameter(info)
    elif info.credentials_provider.lower(
    ) == "BrowserSamlCredentialsProvider".lower():
        provider = BrowserSamlCredentialsProvider()
        provider.add_parameter(info)
    elif info.credentials_provider.lower() == "AdfsCredentialsProvider".lower(
    ):
        provider = AdfsCredentialsProvider()
        provider.add_parameter(info)
    else:
        raise InterfaceError("Invalid credentials provider" +
                             info.credentials_provider)

    credentials: CredentialsHolder = provider.get_credentials()
    metadata: CredentialsHolder.IamMetadata = credentials.get_metadata()
    if metadata is not None:
        auto_create: bool = metadata.get_auto_create()
        db_user: typing.Optional[str] = metadata.get_db_user()
        saml_db_user: typing.Optional[str] = metadata.get_saml_db_user()
        profile_db_user: typing.Optional[str] = metadata.get_profile_db_user()
        db_groups: typing.Optional[str] = metadata.get_db_groups()
        force_lowercase: bool = metadata.get_force_lowercase()
        allow_db_user_override: bool = metadata.get_allow_db_user_override()
        if auto_create is True:
            info.auto_create = auto_create

        if force_lowercase is True:
            info.force_lowercase = force_lowercase

        if allow_db_user_override is True:
            if saml_db_user is not None:
                info.db_user = saml_db_user
            if db_user is not None:
                info.db_user = db_user
            if profile_db_user is not None:
                info.db_user = profile_db_user
        else:
            if db_user is not None:
                info.db_user = db_user
            if profile_db_user is not None:
                info.db_user = profile_db_user
            if saml_db_user is not None:
                info.db_user = saml_db_user

        if (info.db_groups is None) and (db_groups is not None):
            tmp: typing.List[str] = db_groups.split(",")
            info.db_groups = [group.lower() for group in tmp]

    set_cluster_credentials(provider, info)
예제 #10
0
def connect(
    user: str,
    database: str,
    password: str,
    port: int = 5439,
    host: str = "localhost",
    source_address: typing.Optional[str] = None,
    unix_sock: typing.Optional[str] = None,
    ssl: bool = True,
    sslmode: str = "verify-ca",
    timeout: typing.Optional[int] = None,
    max_prepared_statements: int = 1000,
    tcp_keepalive: bool = True,
    application_name: typing.Optional[str] = None,
    replication: typing.Optional[str] = None,
    idp_host: typing.Optional[str] = None,
    db_user: typing.Optional[str] = None,
    app_id: typing.Optional[str] = None,
    app_name: str = "amazon_aws_redshift",
    preferred_role: typing.Optional[str] = None,
    principal_arn: typing.Optional[str] = None,
    credentials_provider: typing.Optional[str] = None,
    region: typing.Optional[str] = None,
    cluster_identifier: typing.Optional[str] = None,
    iam: bool = False,
    client_id: typing.Optional[str] = None,
    idp_tenant: typing.Optional[str] = None,
    client_secret: typing.Optional[str] = None,
    partner_sp_id: typing.Optional[str] = None,
    idp_response_timeout: int = 120,
    listen_port: int = 7890,
    login_url: typing.Optional[str] = None,
    auto_create: bool = False,
    db_groups: typing.List[str] = list(),
    force_lowercase: bool = False,
    allow_db_user_override: bool = False,
    client_protocol_version: int = DEFAULT_PROTOCOL_VERSION,
    database_metadata_current_db_only: bool = True,
    ssl_insecure: typing.Optional[bool] = None,
) -> Connection:

    info: RedshiftProperty = RedshiftProperty()
    set_iam_properties(
        info,
        user=user,
        host=host,
        database=database,
        port=port,
        password=password,
        source_address=source_address,
        unix_sock=unix_sock,
        ssl=ssl,
        sslmode=sslmode,
        timeout=timeout,
        max_prepared_statements=max_prepared_statements,
        tcp_keepalive=tcp_keepalive,
        application_name=application_name,
        replication=replication,
        idp_host=idp_host,
        db_user=db_user,
        app_id=app_id,
        app_name=app_name,
        preferred_role=preferred_role,
        principal_arn=principal_arn,
        credentials_provider=credentials_provider,
        region=region,
        cluster_identifier=cluster_identifier,
        iam=iam,
        client_id=client_id,
        idp_tenant=idp_tenant,
        client_secret=client_secret,
        partner_sp_id=partner_sp_id,
        idp_response_timeout=idp_response_timeout,
        listen_port=listen_port,
        login_url=login_url,
        auto_create=auto_create,
        db_groups=db_groups,
        force_lowercase=force_lowercase,
        allow_db_user_override=allow_db_user_override,
        client_protocol_version=client_protocol_version,
        database_metadata_current_db_only=database_metadata_current_db_only,
        ssl_insecure=ssl_insecure,
    )

    return Connection(
        user=info.user_name,
        host=info.host,
        database=info.db_name,
        port=info.port,
        password=info.password,
        source_address=info.source_address,
        unix_sock=info.unix_sock,
        ssl=info.ssl,
        sslmode=info.sslmode,
        timeout=info.timeout,
        max_prepared_statements=info.max_prepared_statements,
        tcp_keepalive=info.tcp_keepalive,
        application_name=info.application_name,
        replication=info.replication,
        client_protocol_version=info.client_protocol_version,
        database_metadata_current_db_only=database_metadata_current_db_only,
    )
예제 #11
0
    def set_auth_properties(info: RedshiftProperty):
        """
        Helper function to handle IAM and Native Auth connection properties and ensure required parameters are specified.
        Parameters
        """
        import pkg_resources
        from packaging.version import Version

        if info is None:
            raise InterfaceError(
                "Invalid connection property setting. info must be specified")

        # IAM requires an SSL connection to work.
        # Make sure that is set to SSL level VERIFY_CA or higher.
        if info.ssl is True:
            if info.sslmode not in SupportedSSLMode.list():
                info.put("sslmode", SupportedSSLMode.default())
                _logger.debug(
                    "A non-supported value: {} was provides for sslmode. Falling back to default value: {}"
                    .format(info.sslmode, SupportedSSLMode.default()))
        else:
            info.put("sslmode", "")

        # elif (info.iam is False) and any(
        #     (info.credentials_provider, info.access_key_id, info.secret_access_key, info.session_token, info.profile)
        # ):
        #     raise InterfaceError(
        #         "Invalid connection property setting. IAM must be enabled when using credential_provider, "
        #         "AWS credentials, Amazon Redshift authentication profile, or AWS profile"
        #     )
        if info.iam is True:
            _logger.debug("boto3 version: {}".format(
                Version(pkg_resources.get_distribution("boto3").version)))
            _logger.debug("botocore version: {}".format(
                Version(pkg_resources.get_distribution("botocore").version)))

            if info.cluster_identifier is None and not info.is_serverless_host:
                raise InterfaceError(
                    "Invalid connection property setting. cluster_identifier must be provided when IAM is enabled"
                )

            if info.credentials_provider is not None:
                if info.auth_profile is None and any(
                    (info.access_key_id, info.secret_access_key,
                     info.session_token, info.profile)):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide both Credentials provider and "
                        "AWS credentials or AWS profile")
                elif not isinstance(info.credentials_provider, str):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide a non-string value to "
                        "credentials_provider.")
            elif info.profile is not None:
                if info.auth_profile is None and any(
                    (info.access_key_id, info.secret_access_key,
                     info.session_token)):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide any of access_key_id, "
                        "secret_access_key, or session_token when profile is provided"
                    )
            elif info.access_key_id is not None:

                if info.secret_access_key is not None:
                    pass
                elif info.password != "":
                    info.put("secret_access_key", info.password)
                    _logger.debug(
                        "Value of password will be used for secret_access_key")
                else:
                    raise InterfaceError(
                        "Invalid connection property setting. "
                        "secret access key must be provided in either secret_access_key or password field"
                    )

                _logger.debug(
                    "AWS Credentials access_key_id: {} secret_access_key: {} session_token: {}"
                    .format(bool(info.access_key_id),
                            bool(info.secret_access_key),
                            bool(info.session_token)))
            elif info.secret_access_key is not None:
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id is required when secret_access_key is "
                    "provided")
            elif info.session_token is not None:
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id and secret_access_key are  required when "
                    "session_token is provided")

        if info.db_groups and info.force_lowercase:
            info.put("db_groups", [group.lower() for group in info.db_groups])

        # Check for IAM keys and AuthProfile first
        if info.auth_profile is not None:
            if Version(pkg_resources.get_distribution(
                    "boto3").version) < Version("1.17.111"):
                raise pkg_resources.VersionConflict(
                    "boto3 >= 1.17.111 required for authentication via Amazon Redshift authentication profile. "
                    "Please upgrade the installed version of boto3 to use this functionality."
                )

            if not all(
                (info.access_key_id, info.secret_access_key, info.region)):
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id, secret_access_key, and region are required "
                    "for authentication via Redshift auth_profile")
            else:
                # info.put("region", info.region)
                # info.put("endpoint_url", info.endpoint_url)

                resp = IdpAuthHelper.read_auth_profile(
                    auth_profile=typing.cast(str, info.auth_profile),
                    iam_access_key_id=typing.cast(str, info.access_key_id),
                    iam_secret_key=typing.cast(str, info.secret_access_key),
                    iam_session_token=info.session_token,
                    info=info,
                )
                info.put_all(resp)
예제 #12
0
def connect(
    user: typing.Optional[str] = None,
    database: typing.Optional[str] = None,
    password: typing.Optional[str] = None,
    port: typing.Optional[int] = None,
    host: typing.Optional[str] = None,
    source_address: typing.Optional[str] = None,
    unix_sock: typing.Optional[str] = None,
    ssl: typing.Optional[bool] = None,
    sslmode: typing.Optional[str] = None,
    timeout: typing.Optional[int] = None,
    max_prepared_statements: typing.Optional[int] = None,
    tcp_keepalive: typing.Optional[bool] = None,
    application_name: typing.Optional[str] = None,
    replication: typing.Optional[str] = None,
    idp_host: typing.Optional[str] = None,
    db_user: typing.Optional[str] = None,
    app_id: typing.Optional[str] = None,
    app_name: typing.Optional[str] = None,
    preferred_role: typing.Optional[str] = None,
    principal_arn: typing.Optional[str] = None,
    access_key_id: typing.Optional[str] = None,
    secret_access_key: typing.Optional[str] = None,
    session_token: typing.Optional[str] = None,
    profile: typing.Optional[str] = None,
    credentials_provider: typing.Optional[str] = None,
    region: typing.Optional[str] = None,
    cluster_identifier: typing.Optional[str] = None,
    iam: typing.Optional[bool] = None,
    client_id: typing.Optional[str] = None,
    idp_tenant: typing.Optional[str] = None,
    client_secret: typing.Optional[str] = None,
    partner_sp_id: typing.Optional[str] = None,
    idp_response_timeout: typing.Optional[int] = None,
    listen_port: typing.Optional[int] = None,
    login_url: typing.Optional[str] = None,
    auto_create: typing.Optional[bool] = None,
    db_groups: typing.Optional[typing.List[str]] = None,
    force_lowercase: typing.Optional[bool] = None,
    allow_db_user_override: typing.Optional[bool] = None,
    client_protocol_version: typing.Optional[int] = None,
    database_metadata_current_db_only: typing.Optional[bool] = None,
    ssl_insecure: typing.Optional[bool] = None,
    web_identity_token: typing.Optional[str] = None,
    role_session_name: typing.Optional[str] = None,
    role_arn: typing.Optional[str] = None,
    iam_disable_cache: typing.Optional[bool] = None,
    auth_profile: typing.Optional[str] = None,
    endpoint_url: typing.Optional[str] = None,
    provider_name: typing.Optional[str] = None,
    scope: typing.Optional[str] = None,
) -> Connection:
    """
    Establishes a :class:`Connection` to an Amazon Redshift cluster. This function validates user input, optionally authenticates using an identity provider plugin, then constructs a :class:`Connection` object.

    Parameters
    ----------
    user : Optional[str]
        The username to use for authentication with the Amazon Redshift cluster.
    password : Optional[str]
        The password to use for authentication with the Amazon Redshift cluster.
    database : Optional[str]
        The name of the database instance to connect to.
    host : Optional[str]
        The hostname of the Amazon Redshift cluster.
    port : Optional[int]
        The port number of the Amazon Redshift cluster. Default value is 5439.
    source_address : typing.Optional[str]
    unix_sock : Optional[str]
    ssl : Optional[bool]
        Is SSL enabled. Default value is ``True``. SSL must be enabled when authenticating using IAM.
    sslmode : Optional[str]
        The security of the connection to the Amazon Redshift cluster. 'verify-ca' and 'verify-full' are supported.
    timeout : Optional[int]
        The number of seconds before the connection to the server will timeout. By default there is no timeout.
    max_prepared_statements : Optional[int]
    tcp_keepalive : Optional[bool]
        Is `TCP keepalive <https://en.wikipedia.org/wiki/Keepalive#TCP_keepalive>`_ used. The default value is ``True``.
    application_name : Optional[str]
        Sets the application name. The default value is None.
    replication : Optional[str]
        Used to run in `streaming replication mode <https://www.postgresql.org/docs/12/protocol-replication.html>`_.
    idp_host : Optional[str]
        The hostname of the IdP.
    db_user : Optional[str]
        The user ID to use with Amazon Redshift
    app_id : Optional[str]
    app_name : Optional[str]
        The name of the identity provider (IdP) application used for authentication.
    preferred_role : Optional[str]
        The IAM role preferred for the current connection.
    principal_arn : Optional[str]
        The ARN of the IAM entity (user or role) for which you are generating a policy.
    credentials_provider : Optional[str]
        The class name of the IdP that will be used for authenticating with the Amazon Redshift cluster.
    region : Optional[str]
        The AWS region where the Amazon Redshift cluster is located.
    cluster_identifier : Optional[str]
        The cluster identifier of the Amazon Redshift cluster.
    iam : Optional[bool]
        If IAM authentication is enabled. Default value is False. IAM must be True when authenticating using an IdP.
    client_id : Optional[str]
        The client id from Azure IdP.
    idp_tenant : Optional[str]
        The IdP tenant.
    client_secret : Optional[str]
        The client secret from Azure IdP.
    partner_sp_id : Optional[str]
        The Partner SP Id used for authentication with Ping.
    idp_response_timeout : Optional[int]
        The timeout for retrieving SAML assertion from IdP. Default value is `120`.
    listen_port : Optional[int]
        The listen port the IdP will send the SAML assertion to. Default value is `7890`.
    login_url : Optional[str]
        The SSO url for the IdP.
    auto_create : Optional[bool]
        Indicates whether the user should be created if they do not exist. Default value is `False`.
    db_groups : Optional[str]
        A comma-separated list of existing database group names that the `db_user` joins for the current session.
    force_lowercase : Optional[bool]
    allow_db_user_override : Optional[bool]
        Specifies if the driver uses the `db_user` value from the SAML assertion. TDefault value is `False`.
    client_protocol_version : Optional[int]
         The requested server protocol version. The default value is 2 representing `BINARY`. If the requested server protocol cannot be satisfied a warning will be displayed to the user and the driver will default to the highest supported protocol. See `ClientProtocolVersion` for more details.
    database_metadata_current_db_only : Optional[bool]
        Is `datashare <https://docs.aws.amazon.com/redshift/latest/dg/datashare-overview.html>`_ disabled. Default value is True, implying datasharing will not be used.
    ssl_insecure : Optional[bool]
        Specifies if IdP host's server certificate will be verified. Default value is True
    web_identity_token: Optional[str]
        A web identity token used for authentication with JWT.
    role_session_name: Optional[str]
        An identifier for the assumed role session used for authentication with JWT.
    role_arn: Optional[str]
        The role ARN used for authentication with JWT. This parameter is required when using a JWTCredentialsProvider.
    iam_disable_cache: Optional[bool]
        This option specifies whether the IAM credentials are cached. By default caching is enabled.
    auth_profile: Optional[str]
        The name of an Amazon Redshift Authentication profile having connection properties as JSON. See :class:RedshiftProperty to learn how connection properties should be named.
    endpoint_url: Optional[str]
        The Amazon Redshift endpoint url. This option is only used by AWS internal teams.
    provider_name: Optional[str]
        The name of the Redshift Native Auth Provider.
    scope: Optional[str]
        Scope for BrowserAzureOauth2CredentialsProvider authentication.
    Returns
    -------
    A Connection object associated with the specified Amazon Redshift cluster: :class:`Connection`
    """
    info: RedshiftProperty = RedshiftProperty()
    info.put("access_key_id", access_key_id)
    info.put("allow_db_user_override", allow_db_user_override)
    info.put("app_id", app_id)
    info.put("app_name", app_name)
    info.put("application_name", application_name)
    info.put("auth_profile", auth_profile)
    info.put("auto_create", auto_create)
    info.put("client_id", client_id)
    info.put("client_protocol_version", client_protocol_version)
    info.put("client_secret", client_secret)
    info.put("cluster_identifier", cluster_identifier)
    info.put("credentials_provider", credentials_provider)
    info.put("database_metadata_current_db_only",
             database_metadata_current_db_only)
    info.put("db_groups", db_groups)
    info.put("db_name", database)
    info.put("db_user", db_user)
    info.put("endpoint_url", endpoint_url)
    info.put("force_lowercase", force_lowercase)
    info.put("host", host)
    info.put("iam", iam)
    info.put("iam_disable_cache", iam_disable_cache)
    info.put("idp_host", idp_host)
    info.put("idp_response_timeout", idp_response_timeout)
    info.put("idp_tenant", idp_tenant)
    info.put("listen_port", listen_port)
    info.put("login_url", login_url)
    info.put("max_prepared_statements", max_prepared_statements)
    info.put("partner_sp_id", partner_sp_id)
    info.put("password", password)
    info.put("port", port)
    info.put("preferred_role", preferred_role)
    info.put("principal", principal_arn)
    info.put("profile", profile)
    info.put("provider_name", provider_name)
    info.put("region", region)
    info.put("replication", replication)
    info.put("role_arn", role_arn)
    info.put("role_session_name", role_session_name)
    info.put("scope", scope)
    info.put("secret_access_key", secret_access_key)
    info.put("session_token", session_token)
    info.put("source_address", source_address)
    info.put("ssl", ssl)
    info.put("ssl_insecure", ssl_insecure)
    info.put("sslmode", sslmode)
    info.put("tcp_keepalive", tcp_keepalive)
    info.put("timeout", timeout)
    info.put("unix_sock", unix_sock)
    info.put("user_name", user)
    info.put("web_identity_token", web_identity_token)

    _logger.debug(make_divider_block())
    _logger.debug("User provided connection arguments")
    _logger.debug(make_divider_block())
    _logger.debug(mask_secure_info_in_props(info).__str__())
    _logger.debug(make_divider_block())

    if (info.ssl is False) and (info.iam is True):
        raise InterfaceError(
            "Invalid connection property setting. SSL must be enabled when using IAM"
        )

    if (info.iam is False) and (info.ssl_insecure is False):
        raise InterfaceError(
            "Invalid connection property setting. IAM must be enabled when using ssl_insecure"
        )

    if info.client_protocol_version not in ClientProtocolVersion.list():
        raise InterfaceError(
            "Invalid connection property setting. client_protocol_version must be in: {}"
            .format(ClientProtocolVersion.list()))

    redshift_native_auth: bool = False
    if info.iam:
        if info.credentials_provider == "BasicJwtCredentialsProvider":
            redshift_native_auth = True
            _logger.debug("redshift_native_auth enabled")

    if not redshift_native_auth:
        IamHelper.set_iam_properties(info)

    _logger.debug(make_divider_block())
    _logger.debug(
        "Connection arguments following validation and IAM auth (if applicable)"
    )
    _logger.debug(make_divider_block())
    _logger.debug(mask_secure_info_in_props(info).__str__())
    _logger.debug(make_divider_block())

    return Connection(
        user=info.user_name,
        host=info.host,
        database=info.db_name,
        port=info.port,
        password=info.password,
        source_address=info.source_address,
        unix_sock=info.unix_sock,
        ssl=info.ssl,
        sslmode=info.sslmode,
        timeout=info.timeout,
        max_prepared_statements=info.max_prepared_statements,
        tcp_keepalive=info.tcp_keepalive,
        application_name=info.application_name,
        replication=info.replication,
        client_protocol_version=info.client_protocol_version,
        database_metadata_current_db_only=info.
        database_metadata_current_db_only,
        credentials_provider=info.credentials_provider,
        provider_name=info.provider_name,
        web_identity_token=info.web_identity_token,
    )
def connect(
    user: str,
    database: str,
    password: str,
    port: int = 5439,
    host: str = "localhost",
    source_address: typing.Optional[str] = None,
    unix_sock: typing.Optional[str] = None,
    ssl: bool = True,
    sslmode: str = "verify-ca",
    timeout: typing.Optional[int] = None,
    max_prepared_statements: int = 1000,
    tcp_keepalive: bool = True,
    application_name: typing.Optional[str] = None,
    replication: typing.Optional[str] = None,
    idp_host: typing.Optional[str] = None,
    db_user: typing.Optional[str] = None,
    app_id: typing.Optional[str] = None,
    app_name: str = "amazon_aws_redshift",
    preferred_role: typing.Optional[str] = None,
    principal_arn: typing.Optional[str] = None,
    credentials_provider: typing.Optional[str] = None,
    region: typing.Optional[str] = None,
    cluster_identifier: typing.Optional[str] = None,
    iam: bool = False,
    client_id: typing.Optional[str] = None,
    idp_tenant: typing.Optional[str] = None,
    client_secret: typing.Optional[str] = None,
    partner_sp_id: typing.Optional[str] = None,
    idp_response_timeout: int = 120,
    listen_port: int = 7890,
    login_url: typing.Optional[str] = None,
    auto_create: bool = False,
    db_groups: typing.Optional[typing.List[str]] = None,
    force_lowercase: bool = False,
    allow_db_user_override: bool = False,
    log_level: int = 0,
    log_path: str = log_path,
) -> Connection:

    FORMAT_TO_USE: str = "%(levelname)s|%(asctime)s|%(name)s|%(filename)s|" "%(funcName)s|%(lineno)d: %(message)s"
    log_level_dic: typing.Dict[int, int] = {
        0: logging.CRITICAL,
        1: logging.ERROR,
        2: logging.WARN,
        3: logging.INFO,
        4: logging.DEBUG,
    }
    logging.basicConfig(filename=log_path,
                        filemode="w",
                        format=FORMAT_TO_USE,
                        level=logging.INFO)
    logging.disable(log_level_dic[log_level])

    info: RedshiftProperty = RedshiftProperty()
    set_iam_properties(
        info,
        user=user,
        host=host,
        database=database,
        port=port,
        password=password,
        source_address=source_address,
        unix_sock=unix_sock,
        ssl=ssl,
        sslmode=sslmode,
        timeout=timeout,
        max_prepared_statements=max_prepared_statements,
        tcp_keepalive=tcp_keepalive,
        application_name=application_name,
        replication=replication,
        idp_host=idp_host,
        db_user=db_user,
        app_id=app_id,
        app_name=app_name,
        preferred_role=preferred_role,
        principal_arn=principal_arn,
        credentials_provider=credentials_provider,
        region=region,
        cluster_identifier=cluster_identifier,
        iam=iam,
        client_id=client_id,
        idp_tenant=idp_tenant,
        client_secret=client_secret,
        partner_sp_id=partner_sp_id,
        idp_response_timeout=idp_response_timeout,
        listen_port=listen_port,
        login_url=login_url,
        auto_create=auto_create,
        db_groups=db_groups,
        force_lowercase=force_lowercase,
        allow_db_user_override=allow_db_user_override,
    )

    return Connection(
        user=info.user_name,
        host=info.host,
        database=info.db_name,
        port=info.port,
        password=info.password,
        source_address=info.source_address,
        unix_sock=info.unix_sock,
        ssl=info.ssl,
        sslmode=info.sslmode,
        timeout=info.timeout,
        max_prepared_statements=info.max_prepared_statements,
        tcp_keepalive=info.tcp_keepalive,
        application_name=info.application_name,
        replication=info.replication,
    )
예제 #14
0
    def set_iam_credentials(info: RedshiftProperty) -> None:
        """
        Helper function to create the appropriate credential providers.
        """
        klass: typing.Optional[SamlCredentialsProvider] = None
        provider: typing.Union[SamlCredentialsProvider, AWSCredentialsProvider]

        if info.credentials_provider is not None:
            try:
                klass = dynamic_plugin_import(info.credentials_provider)
                provider = klass()  # type: ignore
                provider.add_parameter(info)  # type: ignore
            except (AttributeError, ModuleNotFoundError):
                _logger.debug("Failed to load user defined plugin: {}".format(
                    info.credentials_provider))
                try:
                    klass = dynamic_plugin_import(
                        "redshift_connector.plugin.{}".format(
                            info.credentials_provider))
                    provider = klass()  # type: ignore
                    provider.add_parameter(info)  # type: ignore
                except (AttributeError, ModuleNotFoundError):
                    _logger.debug(
                        "Failed to load pre-defined IdP plugin from redshift_connector.plugin: {}"
                        .format(info.credentials_provider))
                    raise InterfaceError("Invalid credentials provider " +
                                         info.credentials_provider)
        else:  # indicates AWS Credentials will be used
            provider = AWSCredentialsProvider()
            provider.add_parameter(info)

        if isinstance(provider, SamlCredentialsProvider):
            credentials: CredentialsHolder = provider.get_credentials()
            metadata: CredentialsHolder.IamMetadata = credentials.get_metadata(
            )
            if metadata is not None:
                auto_create: bool = metadata.get_auto_create()
                db_user: typing.Optional[str] = metadata.get_db_user()
                saml_db_user: typing.Optional[str] = metadata.get_saml_db_user(
                )
                profile_db_user: typing.Optional[
                    str] = metadata.get_profile_db_user()
                db_groups: typing.List[str] = metadata.get_db_groups()
                force_lowercase: bool = metadata.get_force_lowercase()
                allow_db_user_override: bool = metadata.get_allow_db_user_override(
                )
                if auto_create is True:
                    info.auto_create = auto_create

                if force_lowercase is True:
                    info.force_lowercase = force_lowercase

                if allow_db_user_override is True:
                    if saml_db_user is not None:
                        info.db_user = saml_db_user
                    if db_user is not None:
                        info.db_user = db_user
                    if profile_db_user is not None:
                        info.db_user = profile_db_user
                else:
                    if db_user is not None:
                        info.db_user = db_user
                    if profile_db_user is not None:
                        info.db_user = profile_db_user
                    if saml_db_user is not None:
                        info.db_user = saml_db_user

                if (len(info.db_groups) == 0) and (len(db_groups) > 0):
                    info.db_groups = db_groups

        IamHelper.set_cluster_credentials(provider, info)
def set_iam_properties(
    info: RedshiftProperty,
    user: str,
    host: str,
    database: str,
    port: int,
    password: str,
    source_address: typing.Optional[str],
    unix_sock: typing.Optional[str],
    ssl: bool,
    sslmode: str,
    timeout: typing.Optional[int],
    max_prepared_statements: int,
    tcp_keepalive: bool,
    application_name: typing.Optional[str],
    replication: typing.Optional[str],
    idp_host: typing.Optional[str],
    db_user: typing.Optional[str],
    iam: bool,
    app_id: typing.Optional[str],
    app_name: str,
    preferred_role: typing.Optional[str],
    principal_arn: typing.Optional[str],
    credentials_provider: typing.Optional[str],
    region: typing.Optional[str],
    cluster_identifier: typing.Optional[str],
    client_id: typing.Optional[str],
    idp_tenant: typing.Optional[str],
    client_secret: typing.Optional[str],
    partner_sp_id: typing.Optional[str],
    idp_response_timeout: int,
    listen_port: int,
    login_url: typing.Optional[str],
    auto_create: bool,
    db_groups: typing.Optional[typing.List[str]],
    force_lowercase: bool,
    allow_db_user_override: bool,
) -> None:
    if info is None:
        raise InterfaceError(
            "Invalid connection property setting. info must be specified")
    # IAM requires an SSL connection to work.
    # Make sure that is set to SSL level VERIFY_CA or higher.
    info.ssl = ssl
    if info.ssl is True:
        if sslmode == SSLMode.VERIFY_CA.value:
            info.sslmode = SSLMode.VERIFY_CA.value
        elif sslmode == SSLMode.VERIFY_FULL.value:
            info.sslmode = SSLMode.VERIFY_FULL.value
        else:
            info.sslmode = SSLMode.VERIFY_CA.value
    else:
        info.sslmode = ""

    if (info.ssl is False) and (iam is True):
        raise InterfaceError(
            "Invalid connection property setting. SSL must be enabled when using IAM"
        )
    else:
        info.iam = iam

    if (info.iam is False) and (credentials_provider is not None):
        raise InterfaceError(
            "Invalid connection property setting. IAM must be enabled when using credentials "
            "via identity provider")
    elif (info.iam is True) and (credentials_provider is None):
        raise InterfaceError(
            "Invalid connection property setting. "
            "Credentials provider cannot be None when IAM is enabled")
    else:
        info.credentials_provider = credentials_provider

    if user is None:
        raise InterfaceError(
            "Invalid connection property setting. user must be specified")
    if host is None:
        raise InterfaceError(
            "Invalid connection property setting. host must be specified")
    if database is None:
        raise InterfaceError(
            "Invalid connection property setting. database must be specified")
    if port is None:
        raise InterfaceError(
            "Invalid connection property setting. port must be specified")
    if password is None:
        raise InterfaceError(
            "Invalid connection property setting. password must be specified")

    # basic driver parameters
    info.user_name = user
    info.host = host
    info.db_name = database
    info.port = port
    info.password = password
    info.source_address = source_address
    info.unix_sock = unix_sock
    info.timeout = timeout
    info.max_prepared_statements = max_prepared_statements
    info.tcp_keepalive = tcp_keepalive
    info.application_name = application_name
    info.replication = replication

    # Idp parameters
    info.idp_host = idp_host
    info.db_user = db_user
    info.app_id = app_id
    info.app_name = app_name
    info.preferred_role = preferred_role
    info.principal = principal_arn
    # Regions.fromName(string) requires the string to be lower case and in this format:
    # E.g. "us-west-2"
    info.region = region
    # cluster_identifier parameter is required
    info.cluster_identifier = cluster_identifier
    info.auto_create = auto_create
    info.db_groups = db_groups
    info.force_lowercase = force_lowercase
    info.allow_db_user_override = allow_db_user_override

    # Azure specified parameters
    info.client_id = client_id
    info.idp_tenant = idp_tenant
    info.client_secret = client_secret

    # Browser idp parameters
    info.idp_response_timeout = idp_response_timeout
    info.listen_port = listen_port
    info.login_url = login_url
    info.partner_sp_id = partner_sp_id

    if info.iam is True:
        set_iam_credentials(info)
    else:
        return
    def set_cluster_credentials(cred_provider: typing.Union[
        SamlCredentialsProvider, AWSCredentialsProvider],
                                info: RedshiftProperty) -> None:
        """
        Calls the AWS SDK methods to return temporary credentials.
        The expiration date is returned as the local time set by the client machines OS.
        """
        import boto3  # type: ignore
        import botocore  # type: ignore

        try:
            credentials_holder: typing.Union[
                CredentialsHolder, AWSDirectCredentialsHolder,
                AWSProfileCredentialsHolder] = cred_provider.get_credentials()
            session_credentials: typing.Dict[
                str, str] = credentials_holder.get_session_credentials()

            redshift_client: str = "redshift-serverless" if info.is_serverless_host else "redshift"
            _logger.debug(
                "boto3.client(service_name={}) being used for IAM auth".format(
                    redshift_client))

            for opt_key, opt_val in (("region_name", info.region),
                                     ("endpoint_url", info.endpoint_url)):
                if opt_val is not None:
                    session_credentials[opt_key] = opt_val

            # if AWS credentials were used to create a boto3.Session object, use it
            if credentials_holder.has_associated_session:
                cached_session: boto3.Session = typing.cast(
                    ABCAWSCredentialsHolder,
                    credentials_holder).get_boto_session()
                client = cached_session.client(service_name=redshift_client,
                                               region_name=info.region)
            else:
                client = boto3.client(service_name=redshift_client,
                                      **session_credentials)

            if info.host is None or info.host == "" or info.port is None or info.port == "":
                response: dict

                if info.is_serverless_host:
                    response = client.describe_configuration()
                    info.put("host", response["endpoint"]["address"])
                    info.put("port", response["endpoint"]["port"])
                else:
                    response = client.describe_clusters(
                        ClusterIdentifier=info.cluster_identifier)
                    info.put("host",
                             response["Clusters"][0]["Endpoint"]["Address"])
                    info.put("port",
                             response["Clusters"][0]["Endpoint"]["Port"])

            cred: typing.Optional[typing.Dict[str, typing.Union[
                str, datetime.datetime]]] = None

            if info.iam_disable_cache is False:
                _logger.debug("iam_disable_cache=False")
                # temporary credentials are cached by redshift_connector and will be used if they have not expired
                cache_key: str = IamHelper.get_credentials_cache_key(
                    info, cred_provider)
                cred = IamHelper.credentials_cache.get(cache_key, None)

                _logger.debug(
                    "Searching credential cache for temporary AWS credentials. Found: {} Expiration: {}"
                    .format(
                        bool(cache_key in IamHelper.credentials_cache),
                        cred["Expiration"] if cred is not None else "N/A",
                    ))

            if cred is None or typing.cast(
                    datetime.datetime,
                    cred["Expiration"]) < datetime.datetime.now(tz=tzutc()):
                # retries will occur by default ref:
                # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html#legacy-retry-mode
                _logger.debug(
                    "Credentials expired or not found...requesting from boto")
                if info.is_serverless_host:
                    cred = typing.cast(
                        typing.Dict[str, typing.Union[str, datetime.datetime]],
                        client.get_credentials(dbName=info.db_name, ),
                    )
                    # re-map expiration for compatibility with redshift credential response
                    cred["Expiration"] = cred["expiration"]
                    del cred["expiration"]
                else:
                    cred = typing.cast(
                        typing.Dict[str, typing.Union[str, datetime.datetime]],
                        client.get_cluster_credentials(
                            DbUser=info.db_user,
                            DbName=info.db_name,
                            DbGroups=info.db_groups,
                            ClusterIdentifier=info.cluster_identifier,
                            AutoCreate=info.auto_create,
                        ),
                    )

                if info.iam_disable_cache is False:
                    IamHelper.credentials_cache[cache_key] = typing.cast(
                        typing.Dict[str, typing.Union[str, datetime.datetime]],
                        cred)
            # redshift-serverless api json response payload slightly differs
            if info.is_serverless_host:
                info.put("user_name", typing.cast(str, cred["dbUser"]))
                info.put("password", typing.cast(str, cred["dbPassword"]))
            else:
                info.put("user_name", typing.cast(str, cred["DbUser"]))
                info.put("password", typing.cast(str, cred["DbPassword"]))

            _logger.debug(
                "Using temporary aws credentials with expiration: {}".format(
                    cred.get("Expiration")))

        except botocore.exceptions.ClientError as e:
            _logger.error("ClientError: %s", e)
            raise e
        except Exception as e:
            _logger.error("other Exception: %s", e)
            raise e
def connect(
    user: str,
    database: str,
    password: str,
    port: int = 5439,
    host: str = "localhost",
    source_address: typing.Optional[str] = None,
    unix_sock: typing.Optional[str] = None,
    ssl: bool = True,
    sslmode: str = "verify-ca",
    timeout: typing.Optional[int] = None,
    max_prepared_statements: int = 1000,
    tcp_keepalive: bool = True,
    application_name: typing.Optional[str] = None,
    replication: typing.Optional[str] = None,
    idp_host: typing.Optional[str] = None,
    db_user: typing.Optional[str] = None,
    app_id: typing.Optional[str] = None,
    app_name: str = "amazon_aws_redshift",
    preferred_role: typing.Optional[str] = None,
    principal_arn: typing.Optional[str] = None,
    access_key_id: typing.Optional[str] = None,
    secret_access_key: typing.Optional[str] = None,
    session_token: typing.Optional[str] = None,
    profile: typing.Optional[str] = None,
    credentials_provider: typing.Optional[str] = None,
    region: typing.Optional[str] = None,
    cluster_identifier: typing.Optional[str] = None,
    iam: bool = False,
    client_id: typing.Optional[str] = None,
    idp_tenant: typing.Optional[str] = None,
    client_secret: typing.Optional[str] = None,
    partner_sp_id: typing.Optional[str] = None,
    idp_response_timeout: int = 120,
    listen_port: int = 7890,
    login_url: typing.Optional[str] = None,
    auto_create: bool = False,
    db_groups: typing.List[str] = list(),
    force_lowercase: bool = False,
    allow_db_user_override: bool = False,
    client_protocol_version: int = DEFAULT_PROTOCOL_VERSION,
    database_metadata_current_db_only: bool = True,
    ssl_insecure: typing.Optional[bool] = None,
    web_identity_token: typing.Optional[str] = None,
    role_session_name: typing.Optional[str] = None,
    role_arn: typing.Optional[str] = None,
) -> Connection:
    """
    Establishes a :class:`Connection` to an Amazon Redshift cluster. This function validates user input, optionally authenticates using an identity provider plugin, then constructs a :class:`Connection` object.

    Parameters
    ----------
    user : str
        The username to use for authentication with the Amazon Redshift cluster.
    password : str
        The password to use for authentication with the Amazon Redshift cluster.
    database : str
        The name of the database instance to connect to.
    host : str
        The hostname of the Amazon Redshift cluster.
    port : int
        The port number of the Amazon Redshift cluster. Default value is 5439.
    source_address : typing.Optional[str]
    unix_sock : Optional[str]
    ssl : bool
        Is SSL enabled. Default value is ``True``. SSL must be enabled when authenticating using IAM.
    sslmode : str
        The security of the connection to the Amazon Redshift cluster. 'verify-ca' and 'verify-full' are supported.
    timeout : Optional[int]
        The number of seconds before the connection to the server will timeout. By default there is no timeout.
    max_prepared_statements : int
    tcp_keepalive : Optional[bool]
        Is `TCP keepalive <https://en.wikipedia.org/wiki/Keepalive#TCP_keepalive>`_ used. The default value is ``True``.
    application_name : Optional[str]
        Sets the application name. The default value is None.
    replication : Optional[str]
        Used to run in `streaming replication mode <https://www.postgresql.org/docs/12/protocol-replication.html>`_.
    idp_host : Optional[str]
        The hostname of the IdP.
    db_user : str
        The user ID to use with Amazon Redshift
    app_id : Optional[str]
    app_name : str
        The name of the identity provider (IdP) application used for authentication.
    preferred_role : str
        The IAM role preferred for the current connection.
    principal_arn : Optional[str]
    credentials_provider : str
        The class name of the IdP that will be used for authenticating with the Amazon Redshift cluster.
    region : str
        The AWS region where the Amazon Redshift cluster is located.
    cluster_identifier : str
        The cluster identifier of the Amazon Redshift cluster.
    iam : bool
        If IAM authentication is enabled. Default value is False. IAM must be True when authenticating using an IdP.
    client_id : str
        The client id from Azure IdP.
    idp_tenant : str
        The IdP tenant.
    client_secret : str
        The client secret from Azure IdP.
    partner_sp_id : Optional[str]
    idp_response_timeout : int
        The timeout for retrieving SAML assertion from IdP. Default value is `120`.
    listen_port : int
        The listen port the IdP will send the SAML assertion to. Default value is `7890`.
    login_url : str
        The SSO url for the IdP.
    auto_create :bool
        Indicates whether the user should be created if they do not exist. Default value is `False`.
    db_groups : str
        A comma-separated list of existing database group names that the `db_user` joins for the current session.
    force_lowercase :
    allow_db_user_override : bool
        Specifies if the driver uses the `db_user` value from the SAML assertion. TDefault value is `False`.
    client_protocol_version : int
         The requested server protocol version. The default value is 1 representing `EXTENDED_RESULT_METADATA`. If the requested server protocol cannot be satisfied, a warning will be displayed to the user.
    database_metadata_current_db_only : bool
        Is `datashare <https://docs.aws.amazon.com/redshift/latest/dg/datashare-overview.html>`_ disabled. Default value is True, implying datasharing will not be used.
    ssl_insecure : bool
        Specifies if IdP host's server certificate will be verified. Default value is True

    Returns
    -------
    A Connection object associated with the specified Amazon Redshift cluster: :class:`Connection`
    """
    info: RedshiftProperty = RedshiftProperty()
    IamHelper.set_iam_properties(
        info,
        user=user,
        host=host,
        database=database,
        port=port,
        password=password,
        source_address=source_address,
        unix_sock=unix_sock,
        ssl=ssl,
        sslmode=sslmode,
        timeout=timeout,
        max_prepared_statements=max_prepared_statements,
        tcp_keepalive=tcp_keepalive,
        application_name=application_name,
        replication=replication,
        idp_host=idp_host,
        db_user=db_user,
        app_id=app_id,
        app_name=app_name,
        preferred_role=preferred_role,
        principal_arn=principal_arn,
        access_key_id=access_key_id,
        secret_access_key=secret_access_key,
        session_token=session_token,
        profile=profile,
        credentials_provider=credentials_provider,
        region=region,
        cluster_identifier=cluster_identifier,
        iam=iam,
        client_id=client_id,
        idp_tenant=idp_tenant,
        client_secret=client_secret,
        partner_sp_id=partner_sp_id,
        idp_response_timeout=idp_response_timeout,
        listen_port=listen_port,
        login_url=login_url,
        auto_create=auto_create,
        db_groups=db_groups,
        force_lowercase=force_lowercase,
        allow_db_user_override=allow_db_user_override,
        client_protocol_version=client_protocol_version,
        database_metadata_current_db_only=database_metadata_current_db_only,
        ssl_insecure=ssl_insecure,
        web_identity_token=web_identity_token,
        role_session_name=role_session_name,
        role_arn=role_arn,
    )

    return Connection(
        user=info.user_name,
        host=info.host,
        database=info.db_name,
        port=info.port,
        password=info.password,
        source_address=info.source_address,
        unix_sock=info.unix_sock,
        ssl=info.ssl,
        sslmode=info.sslmode,
        timeout=info.timeout,
        max_prepared_statements=info.max_prepared_statements,
        tcp_keepalive=info.tcp_keepalive,
        application_name=info.application_name,
        replication=info.replication,
        client_protocol_version=info.client_protocol_version,
        database_metadata_current_db_only=database_metadata_current_db_only,
    )
예제 #18
0
    def set_iam_properties(
        info: RedshiftProperty,
        user: str,
        host: str,
        database: str,
        port: int,
        password: str,
        source_address: typing.Optional[str],
        unix_sock: typing.Optional[str],
        ssl: bool,
        sslmode: str,
        timeout: typing.Optional[int],
        max_prepared_statements: int,
        tcp_keepalive: bool,
        application_name: typing.Optional[str],
        replication: typing.Optional[str],
        idp_host: typing.Optional[str],
        db_user: typing.Optional[str],
        iam: bool,
        app_id: typing.Optional[str],
        app_name: str,
        preferred_role: typing.Optional[str],
        principal_arn: typing.Optional[str],
        access_key_id: typing.Optional[str],
        secret_access_key: typing.Optional[str],
        session_token: typing.Optional[str],
        profile: typing.Optional[str],
        credentials_provider: typing.Optional[str],
        region: typing.Optional[str],
        cluster_identifier: typing.Optional[str],
        client_id: typing.Optional[str],
        idp_tenant: typing.Optional[str],
        client_secret: typing.Optional[str],
        partner_sp_id: typing.Optional[str],
        idp_response_timeout: int,
        listen_port: int,
        login_url: typing.Optional[str],
        auto_create: bool,
        db_groups: typing.List[str],
        force_lowercase: bool,
        allow_db_user_override: bool,
        client_protocol_version: int,
        database_metadata_current_db_only: bool,
        ssl_insecure: typing.Optional[bool],
        web_identity_token: typing.Optional[str],
        role_session_name: typing.Optional[str],
        role_arn: typing.Optional[str],
    ) -> None:
        """
        Helper function to handle IAM connection properties and ensure required parameters are specified.
        Parameters
        """
        if info is None:
            raise InterfaceError(
                "Invalid connection property setting. info must be specified")
        # IAM requires an SSL connection to work.
        # Make sure that is set to SSL level VERIFY_CA or higher.
        info.ssl = ssl
        if info.ssl is True:
            if sslmode not in SupportedSSLMode.list():
                info.sslmode = SupportedSSLMode.default()
                _logger.debug(
                    "A non-supported value: {} was provides for sslmode. Falling back to default value: {}"
                    .format(sslmode, SupportedSSLMode.default()))
            else:
                info.sslmode = sslmode
        else:
            info.sslmode = ""

        if (info.ssl is False) and (iam is True):
            raise InterfaceError(
                "Invalid connection property setting. SSL must be enabled when using IAM"
            )
        else:
            info.iam = iam

        if (info.iam is False) and (ssl_insecure is not None):
            raise InterfaceError(
                "Invalid connection property setting. IAM must be enabled when using ssl_insecure"
            )
        elif (info.iam is False) and any(
            (credentials_provider, access_key_id, secret_access_key,
             session_token, profile)):
            raise InterfaceError(
                "Invalid connection property setting. IAM must be enabled when using credential_provider, "
                "AWS credentials, or AWS profile")
        elif info.iam is True:
            if cluster_identifier is None:
                raise InterfaceError(
                    "Invalid connection property setting. cluster_identifier must be provided when IAM is enabled"
                )
            if not any((credentials_provider, access_key_id, secret_access_key,
                        session_token, profile)):
                raise InterfaceError(
                    "Invalid connection property setting. Credentials provider, AWS credentials, or AWS profile must be"
                    " provided when IAM is enabled")
            elif credentials_provider is not None:
                if any((access_key_id, secret_access_key, session_token,
                        profile)):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide both Credentials provider and "
                        "AWS credentials or AWS profile")
                else:
                    info.credentials_provider = credentials_provider
            elif profile is not None:
                if any((access_key_id, secret_access_key, session_token)):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide any of access_key_id, "
                        "secret_access_key, or session_token when profile is provided"
                    )
                else:
                    info.profile = profile
            elif access_key_id is not None:
                info.access_key_id = access_key_id

                if secret_access_key is not None:
                    info.secret_access_key = secret_access_key
                # secret_access_key can be provided in the password field so it is hidden from applications such as
                # SQL Workbench.
                elif password != "":
                    info.secret_access_key = password
                else:
                    raise InterfaceError(
                        "Invalid connection property setting. "
                        "secret access key must be provided in either secret_access_key or password field"
                    )

                if session_token is not None:
                    info.session_token = session_token
            elif secret_access_key is not None:
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id is required when secret_access_key is "
                    "provided")
            elif session_token is not None:
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id and secret_access_key are  required when "
                    "session_token is provided")

        if not all((user, host, database, port, password)):
            if (profile is None) and (access_key_id is None):
                raise InterfaceError(
                    "Invalid connection property setting. "
                    "user, password, host, database and port are required "
                    "when not using AWS credentials and AWS profile")

        if client_protocol_version not in ClientProtocolVersion.list():
            raise InterfaceError(
                "Invalid connection property setting. client_protocol_version must be in: {}"
                .format(ClientProtocolVersion.list()))

        # basic driver parameters
        info.user_name = user
        info.host = host
        info.db_name = database
        info.port = port
        info.password = password
        info.source_address = source_address
        info.unix_sock = unix_sock
        info.timeout = timeout
        info.max_prepared_statements = max_prepared_statements
        info.tcp_keepalive = tcp_keepalive
        info.application_name = application_name
        info.replication = replication
        info.client_protocol_version = client_protocol_version
        info.database_metadata_current_db_only = database_metadata_current_db_only

        # Idp parameters
        info.idp_host = idp_host
        info.db_user = db_user
        info.app_id = app_id
        info.app_name = app_name
        info.preferred_role = preferred_role
        info.principal = principal_arn
        # Regions.fromName(string) requires the string to be lower case and in this format:
        # E.g. "us-west-2"
        info.region = region
        # cluster_identifier parameter is required
        info.cluster_identifier = cluster_identifier
        info.auto_create = auto_create
        info.db_groups = db_groups
        info.force_lowercase = force_lowercase
        info.allow_db_user_override = allow_db_user_override

        if ssl_insecure is not None:
            info.sslInsecure = ssl_insecure

        # Azure specified parameters
        info.client_id = client_id
        info.idp_tenant = idp_tenant
        info.client_secret = client_secret

        # Browser idp parameters
        info.idp_response_timeout = idp_response_timeout
        info.listen_port = listen_port
        info.login_url = login_url
        info.partner_sp_id = partner_sp_id

        # Jwt idp parameters
        info.web_identity_token = web_identity_token
        info.role_session_name = role_session_name
        info.role_arn = role_arn

        if info.iam is True:
            IamHelper.set_iam_credentials(info)
        else:
            return
예제 #19
0
    def set_cluster_credentials(cred_provider: typing.Union[
        SamlCredentialsProvider, AWSCredentialsProvider],
                                info: RedshiftProperty) -> None:
        """
        Calls the AWS SDK methods to return temporary credentials.
        The expiration date is returned as the local time set by the client machines OS.
        """
        import boto3  # type: ignore
        import botocore  # type: ignore

        try:
            credentials_holder: typing.Union[
                CredentialsHolder, AWSDirectCredentialsHolder,
                AWSProfileCredentialsHolder] = cred_provider.get_credentials()
            session_credentials: typing.Dict[
                str, str] = credentials_holder.get_session_credentials()

            if info.region is not None:
                session_credentials["region_name"] = info.region

            # if AWS credentials were used to create a boto3.Session object, use it
            if credentials_holder.has_associated_session:
                cached_session: boto3.Session = typing.cast(
                    ABCAWSCredentialsHolder,
                    credentials_holder).get_boto_session()
                client = cached_session.client(service_name="redshift",
                                               region_name=info.region)
            else:
                client = boto3.client(service_name="redshift",
                                      **session_credentials)

            response = client.describe_clusters(
                ClusterIdentifier=info.cluster_identifier)
            info.host = response["Clusters"][0]["Endpoint"]["Address"]
            info.port = response["Clusters"][0]["Endpoint"]["Port"]

            # temporary credentials are cached by redshift_connector and will be used if they have not expired
            cache_key: str = IamHelper.get_credentials_cache_key(info)
            cred: typing.Optional[typing.Dict[str, typing.Union[
                str, datetime.datetime]]] = IamHelper.credentials_cache.get(
                    cache_key, None)

            _logger.debug(
                "Searching credential cache for temporary AWS credentials. Found: {} Expiration: {}"
                .format(bool(cache_key in IamHelper.credentials_cache),
                        cred["Expiration"] if cred is not None else "N/A"))

            if cred is None or typing.cast(
                    datetime.datetime,
                    cred["Expiration"]) < datetime.datetime.now(tz=tzutc()):
                # retries will occur by default ref:
                # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html#legacy-retry-mode
                cred = typing.cast(
                    typing.Dict[str, typing.Union[str, datetime.datetime]],
                    client.get_cluster_credentials(
                        DbUser=info.db_user,
                        DbName=info.db_name,
                        DbGroups=info.db_groups,
                        ClusterIdentifier=info.cluster_identifier,
                        AutoCreate=info.auto_create,
                    ),
                )

                IamHelper.credentials_cache[cache_key] = typing.cast(
                    typing.Dict[str, typing.Union[str, datetime.datetime]],
                    cred)

            info.user_name = typing.cast(str, cred["DbUser"])
            info.password = typing.cast(str, cred["DbPassword"])

            _logger.debug(
                "Using temporary aws credentials with expiration: {}".format(
                    cred["Expiration"]))

        except botocore.exceptions.ClientError as e:
            _logger.error("ClientError: %s", e)
            raise e
        except client.exceptions.ClusterNotFoundFault as e:
            _logger.error("ClusterNotFoundFault: %s", e)
            raise e
        except client.exceptions.UnsupportedOperationFault as e:
            _logger.error("UnsupportedOperationFault: %s", e)
            raise e
        except botocore.exceptions.EndpointConnectionError as e:
            _logger.error("EndpointConnectionError: %s", e)
            raise e
        except Exception as e:
            _logger.error("other Exception: %s", e)
            raise e