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
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
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, ), ))
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
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))
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
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)
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, )
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)
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, )
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, )
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
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