Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot use VPC Endpoint for Redshift Serverless #246

Open
pitchayasak opened this issue Dec 2, 2024 · 0 comments
Open

Cannot use VPC Endpoint for Redshift Serverless #246

pitchayasak opened this issue Dec 2, 2024 · 0 comments

Comments

@pitchayasak
Copy link

Driver version

2.1.4

Redshift version

PostgreSQL 8.0.2 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3), Redshift 1.0.79237

Client Operating System

Ubuntu 22.04.4 LTS

Python version

3.11.9

Problem description

  1. Expected behaviour: Create connection to Redshift Serverless with VPC Endpoint
  2. Actual behaviour: SSL validation failed
  3. Error message/stack trace:
    SSLError: SSL validation failed for https://vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com/ hostname 'vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com' doesn't match 'redshift-serverless.ap-southeast-1.amazonaws.com'

Python Driver trace logs

---------------------------------------------------------------------------
CertificateError                          Traceback (most recent call last)
File /opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:715, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
    714 # Make the request on the httplib connection object.
--> 715 httplib_response = self._make_request(
    716     conn,
    717     method,
    718     url,
    719     timeout=timeout_obj,
    720     body=body,
    721     headers=headers,
    722     chunked=chunked,
    723 )
    725 # If we're going to release the connection in ``finally:``, then
    726 # the response doesn't need to know about the connection. Otherwise
    727 # it will also try to release it and we'll have a double-release
    728 # mess.

File /opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:404, in HTTPConnectionPool._make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw)
    403 try:
--> 404     self._validate_conn(conn)
    405 except (SocketTimeout, BaseSSLError) as e:
    406     # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout.

File /opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1060, in HTTPSConnectionPool._validate_conn(self, conn)
   1059 if not getattr(conn, "sock", None):  # AppEngine might not have  `.sock`
-> 1060     conn.connect()
   1062 if not conn.is_verified:

File /opt/conda/lib/python3.11/site-packages/urllib3/connection.py:472, in HTTPSConnection.connect(self)
    463         warnings.warn(
    464             (
    465                 "Certificate for {0} has no `subjectAltName`, falling back to check for a "
   (...)
    470             SubjectAltNameWarning,
    471         )
--> 472     _match_hostname(cert, self.assert_hostname or server_hostname)
    474 self.is_verified = (
    475     context.verify_mode == ssl.CERT_REQUIRED
    476     or self.assert_fingerprint is not None
    477 )

File /opt/conda/lib/python3.11/site-packages/urllib3/connection.py:545, in _match_hostname(cert, asserted_hostname)
    544 try:
--> 545     match_hostname(cert, asserted_hostname)
    546 except CertificateError as e:

File /opt/conda/lib/python3.11/site-packages/urllib3/util/ssl_match_hostname.py:155, in match_hostname(cert, hostname)
    154 elif len(dnsnames) == 1:
--> 155     raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0]))
    156 else:

CertificateError: hostname 'vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com' doesn't match 'redshift-serverless.ap-southeast-1.amazonaws.com'

During handling of the above exception, another exception occurred:

SSLError                                  Traceback (most recent call last)
File /opt/conda/lib/python3.11/site-packages/botocore/httpsession.py:464, in URLLib3Session.send(self, request)
    463 request_target = self._get_request_target(request.url, proxy_url)
--> 464 urllib_response = conn.urlopen(
    465     method=request.method,
    466     url=request_target,
    467     body=request.body,
    468     headers=request.headers,
    469     retries=Retry(False),
    470     assert_same_host=False,
    471     preload_content=False,
    472     decode_content=False,
    473     chunked=self._chunked(request.headers),
    474 )
    476 http_response = botocore.awsrequest.AWSResponse(
    477     request.url,
    478     urllib_response.status,
    479     urllib_response.headers,
    480     urllib_response,
    481 )

File /opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:801, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
    799     e = ProtocolError("Connection aborted.", e)
--> 801 retries = retries.increment(
    802     method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
    803 )
    804 retries.sleep()

File /opt/conda/lib/python3.11/site-packages/urllib3/util/retry.py:527, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    525 if self.total is False and error:
    526     # Disabled, indicate to re-raise the error.
--> 527     raise six.reraise(type(error), error, _stacktrace)
    529 total = self.total

File /opt/conda/lib/python3.11/site-packages/urllib3/packages/six.py:769, in reraise(tp, value, tb)
    768 if value.__traceback__ is not tb:
--> 769     raise value.with_traceback(tb)
    770 raise value

File /opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:715, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
    714 # Make the request on the httplib connection object.
--> 715 httplib_response = self._make_request(
    716     conn,
    717     method,
    718     url,
    719     timeout=timeout_obj,
    720     body=body,
    721     headers=headers,
    722     chunked=chunked,
    723 )
    725 # If we're going to release the connection in ``finally:``, then
    726 # the response doesn't need to know about the connection. Otherwise
    727 # it will also try to release it and we'll have a double-release
    728 # mess.

File /opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:404, in HTTPConnectionPool._make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw)
    403 try:
--> 404     self._validate_conn(conn)
    405 except (SocketTimeout, BaseSSLError) as e:
    406     # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout.

File /opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1060, in HTTPSConnectionPool._validate_conn(self, conn)
   1059 if not getattr(conn, "sock", None):  # AppEngine might not have  `.sock`
-> 1060     conn.connect()
   1062 if not conn.is_verified:

File /opt/conda/lib/python3.11/site-packages/urllib3/connection.py:472, in HTTPSConnection.connect(self)
    463         warnings.warn(
    464             (
    465                 "Certificate for {0} has no `subjectAltName`, falling back to check for a "
   (...)
    470             SubjectAltNameWarning,
    471         )
--> 472     _match_hostname(cert, self.assert_hostname or server_hostname)
    474 self.is_verified = (
    475     context.verify_mode == ssl.CERT_REQUIRED
    476     or self.assert_fingerprint is not None
    477 )

File /opt/conda/lib/python3.11/site-packages/urllib3/connection.py:545, in _match_hostname(cert, asserted_hostname)
    544 try:
--> 545     match_hostname(cert, asserted_hostname)
    546 except CertificateError as e:

File /opt/conda/lib/python3.11/site-packages/urllib3/util/ssl_match_hostname.py:155, in match_hostname(cert, hostname)
    154 elif len(dnsnames) == 1:
--> 155     raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0]))
    156 else:

SSLError: hostname 'vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com' doesn't match 'redshift-serverless.ap-southeast-1.amazonaws.com'

During handling of the above exception, another exception occurred:

SSLError                                  Traceback (most recent call last)
Cell In[5], line 1
----> 1 conn = redshift_connector.connect(
      2      host=host,
      3      port=5439,
      4      database=db_name,
      5      iam=True,
      6      endpoint_url='https://vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com'
      7   )
      9 conn.autocommit = True
     10 # conn.run("VACUUM")

File /opt/conda/lib/python3.11/site-packages/redshift_connector/__init__.py:386, in connect(user, database, password, port, host, source_address, unix_sock, ssl, sslmode, timeout, max_prepared_statements, tcp_keepalive, application_name, replication, idp_host, db_user, app_id, app_name, preferred_role, principal_arn, access_key_id, secret_access_key, session_token, profile, credentials_provider, region, cluster_identifier, iam, client_id, idp_tenant, client_secret, partner_sp_id, idp_response_timeout, listen_port, login_to_rp, login_url, auto_create, db_groups, force_lowercase, allow_db_user_override, client_protocol_version, database_metadata_current_db_only, ssl_insecure, web_identity_token, role_session_name, role_arn, iam_disable_cache, auth_profile, endpoint_url, provider_name, scope, numeric_to_float, is_serverless, serverless_acct_id, serverless_work_group, group_federation, identity_namespace, idc_client_display_name, idc_region, issuer_url, token, token_type)
    383         _logger.debug("redshift_native_auth enabled")
    385 if not redshift_native_auth:
--> 386     IamHelper.set_iam_properties(info)
    388 _logger.debug(make_divider_block())
    389 _logger.debug("Connection arguments following validation and IAM auth (if applicable)")

File /opt/conda/lib/python3.11/site-packages/redshift_connector/iam_helper.py:149, in IamHelper.set_iam_properties(info)
    145     if info.cluster_identifier is None and not info._is_serverless and not info.is_cname:
    146         raise InterfaceError(
    147             "Invalid connection property setting. cluster_identifier must be provided when IAM is enabled"
    148         )
--> 149     IamHelper.set_iam_credentials(info)
    150 # Check for Browser based OAuth Native authentication
    151 NativeAuthPluginHelper.set_native_auth_plugin_properties(info)

File /opt/conda/lib/python3.11/site-packages/redshift_connector/iam_helper.py:230, in IamHelper.set_iam_credentials(info)
    227     IamHelper.set_cluster_identifier(provider, info)
    229 # Redshift database credentials  will be determined using the redshift client from boto3 API
--> 230 IamHelper.set_cluster_credentials(provider, info)
    232 # Redshift instance host and port must be retrieved
    233 IamHelper.set_cluster_host_and_port(provider, info)

File /opt/conda/lib/python3.11/site-packages/redshift_connector/iam_helper.py:460, in IamHelper.set_cluster_credentials(cred_provider, info)
    456     get_cred_args["customDomainName"] = info.host
    457 _logger.debug("Calling get_credentials with parameters %s", get_cred_args)
    458 cred = typing.cast(
    459     typing.Dict[str, typing.Union[str, datetime.datetime]],
--> 460     client.get_credentials(**get_cred_args),
    461 )
    462 # re-map expiration for compatibility with redshift credential response
    463 cred["Expiration"] = cred["expiration"]

File /opt/conda/lib/python3.11/site-packages/botocore/client.py:565, in ClientCreator._create_api_method.<locals>._api_call(self, *args, **kwargs)
    561     raise TypeError(
    562         f"{py_operation_name}() only accepts keyword arguments."
    563     )
    564 # The "self" in this scope is referring to the BaseClient.
--> 565 return self._make_api_call(operation_name, kwargs)

File /opt/conda/lib/python3.11/site-packages/botocore/client.py:1001, in BaseClient._make_api_call(self, operation_name, api_params)
    997     maybe_compress_request(
    998         self.meta.config, request_dict, operation_model
    999     )
   1000     apply_request_checksum(request_dict)
-> 1001     http, parsed_response = self._make_request(
   1002         operation_model, request_dict, request_context
   1003     )
   1005 self.meta.events.emit(
   1006     'after-call.{service_id}.{operation_name}'.format(
   1007         service_id=service_id, operation_name=operation_name
   (...)
   1012     context=request_context,
   1013 )
   1015 if http.status_code >= 300:

File /opt/conda/lib/python3.11/site-packages/botocore/client.py:1027, in BaseClient._make_request(self, operation_model, request_dict, request_context)
   1025 def _make_request(self, operation_model, request_dict, request_context):
   1026     try:
-> 1027         return self._endpoint.make_request(operation_model, request_dict)
   1028     except Exception as e:
   1029         self.meta.events.emit(
   1030             'after-call-error.{service_id}.{operation_name}'.format(
   1031                 service_id=self._service_model.service_id.hyphenize(),
   (...)
   1035             context=request_context,
   1036         )

File /opt/conda/lib/python3.11/site-packages/botocore/endpoint.py:119, in Endpoint.make_request(self, operation_model, request_dict)
    113 def make_request(self, operation_model, request_dict):
    114     logger.debug(
    115         "Making request for %s with params: %s",
    116         operation_model,
    117         request_dict,
    118     )
--> 119     return self._send_request(request_dict, operation_model)

File /opt/conda/lib/python3.11/site-packages/botocore/endpoint.py:202, in Endpoint._send_request(self, request_dict, operation_model)
    198 request = self.create_request(request_dict, operation_model)
    199 success_response, exception = self._get_response(
    200     request, operation_model, context
    201 )
--> 202 while self._needs_retry(
    203     attempts,
    204     operation_model,
    205     request_dict,
    206     success_response,
    207     exception,
    208 ):
    209     attempts += 1
    210     self._update_retries_context(context, attempts, success_response)

File /opt/conda/lib/python3.11/site-packages/botocore/endpoint.py:354, in Endpoint._needs_retry(self, attempts, operation_model, request_dict, response, caught_exception)
    352 service_id = operation_model.service_model.service_id.hyphenize()
    353 event_name = f"needs-retry.{service_id}.{operation_model.name}"
--> 354 responses = self._event_emitter.emit(
    355     event_name,
    356     response=response,
    357     endpoint=self,
    358     operation=operation_model,
    359     attempts=attempts,
    360     caught_exception=caught_exception,
    361     request_dict=request_dict,
    362 )
    363 handler_response = first_non_none_response(responses)
    364 if handler_response is None:

File /opt/conda/lib/python3.11/site-packages/botocore/hooks.py:412, in EventAliaser.emit(self, event_name, **kwargs)
    410 def emit(self, event_name, **kwargs):
    411     aliased_event_name = self._alias_event_name(event_name)
--> 412     return self._emitter.emit(aliased_event_name, **kwargs)

File /opt/conda/lib/python3.11/site-packages/botocore/hooks.py:256, in HierarchicalEmitter.emit(self, event_name, **kwargs)
    245 def emit(self, event_name, **kwargs):
    246     """
    247     Emit an event by name with arguments passed as keyword args.
    248 
   (...)
    254              handlers.
    255     """
--> 256     return self._emit(event_name, kwargs)

File /opt/conda/lib/python3.11/site-packages/botocore/hooks.py:239, in HierarchicalEmitter._emit(self, event_name, kwargs, stop_on_response)
    237 for handler in handlers_to_call:
    238     logger.debug('Event %s: calling handler %s', event_name, handler)
--> 239     response = handler(**kwargs)
    240     responses.append((handler, response))
    241     if stop_on_response and response is not None:

File /opt/conda/lib/python3.11/site-packages/botocore/retryhandler.py:207, in RetryHandler.__call__(self, attempts, response, caught_exception, **kwargs)
    204     retries_context = kwargs['request_dict']['context'].get('retries')
    205     checker_kwargs.update({'retries_context': retries_context})
--> 207 if self._checker(**checker_kwargs):
    208     result = self._action(attempts=attempts)
    209     logger.debug("Retry needed, action of: %s", result)

File /opt/conda/lib/python3.11/site-packages/botocore/retryhandler.py:284, in MaxAttemptsDecorator.__call__(self, attempt_number, response, caught_exception, retries_context)
    279 if retries_context:
    280     retries_context['max'] = max(
    281         retries_context.get('max', 0), self._max_attempts
    282     )
--> 284 should_retry = self._should_retry(
    285     attempt_number, response, caught_exception
    286 )
    287 if should_retry:
    288     if attempt_number >= self._max_attempts:
    289         # explicitly set MaxAttemptsReached

File /opt/conda/lib/python3.11/site-packages/botocore/retryhandler.py:320, in MaxAttemptsDecorator._should_retry(self, attempt_number, response, caught_exception)
    316         return True
    317 else:
    318     # If we've exceeded the max attempts we just let the exception
    319     # propagate if one has occurred.
--> 320     return self._checker(attempt_number, response, caught_exception)

File /opt/conda/lib/python3.11/site-packages/botocore/retryhandler.py:363, in MultiChecker.__call__(self, attempt_number, response, caught_exception)
    361 def __call__(self, attempt_number, response, caught_exception):
    362     for checker in self._checkers:
--> 363         checker_response = checker(
    364             attempt_number, response, caught_exception
    365         )
    366         if checker_response:
    367             return checker_response

File /opt/conda/lib/python3.11/site-packages/botocore/retryhandler.py:247, in BaseChecker.__call__(self, attempt_number, response, caught_exception)
    245     return self._check_response(attempt_number, response)
    246 elif caught_exception is not None:
--> 247     return self._check_caught_exception(
    248         attempt_number, caught_exception
    249     )
    250 else:
    251     raise ValueError("Both response and caught_exception are None.")

File /opt/conda/lib/python3.11/site-packages/botocore/retryhandler.py:416, in ExceptionRaiser._check_caught_exception(self, attempt_number, caught_exception)
    408 def _check_caught_exception(self, attempt_number, caught_exception):
    409     # This is implementation specific, but this class is useful by
    410     # coordinating with the MaxAttemptsDecorator.
   (...)
    414     # the MaxAttemptsDecorator is not interested in retrying the exception
    415     # then this exception just propagates out past the retry code.
--> 416     raise caught_exception

File /opt/conda/lib/python3.11/site-packages/botocore/endpoint.py:281, in Endpoint._do_get_response(self, request, operation_model, context)
    279     http_response = first_non_none_response(responses)
    280     if http_response is None:
--> 281         http_response = self._send(request)
    282 except HTTPClientError as e:
    283     return (None, e)

File /opt/conda/lib/python3.11/site-packages/botocore/endpoint.py:377, in Endpoint._send(self, request)
    376 def _send(self, request):
--> 377     return self.http_session.send(request)

File /opt/conda/lib/python3.11/site-packages/botocore/httpsession.py:491, in URLLib3Session.send(self, request)
    489     return http_response
    490 except URLLib3SSLError as e:
--> 491     raise SSLError(endpoint_url=request.url, error=e)
    492 except (NewConnectionError, socket.gaierror) as e:
    493     raise EndpointConnectionError(endpoint_url=request.url, error=e)

SSLError: SSL validation failed for https://vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com/ hostname 'vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com' doesn't match 'redshift-serverless.ap-southeast-1.amazonaws.com'

Reproduction code

conn = redshift_connector.connect(
     host=host,
     port=5439,
     database=db_name,
     iam=True,
     endpoint_url='https://vpce-06c30eb85ac87559f-vu0ekkrw.redshift-serverless.ap-southeast-1.vpce.amazonaws.com'
  )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant