aws / aws/amazon-redshift-python-driver

Cannot use VPC Endpoint for Redshift Serverless

Abierto
#246 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Python
Estrellas
220
Forks
86
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

## 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.._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'
)
```

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.