Skip to content

jmp: exception stack-trace dump when got Certificate Error #175

@maboras-rh

Description

@maboras-rh

Issue:
jmp login command dump a full error stack-trace when ClientConnectorCertificateError occur,

Cause:
Root CA certificate was not correctly defined by Python installation in local dev environment.

Fix:
It works after creating local Root CA Certificates or run script Install Certificates.command from Python installation package.

❯ uname -a
Darwin maboras-mac 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:56 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6041 arm64

❯ jmp version
Jumpstarter v0.1.dev2902+g5489b794d from /Users/maboras/rh_workspace/jumpstarter-dev/jumpstarter/python/packages/jumpstarter-cli-common (Python 3.13.11 (main, Dec 5 2025, 16:06:33) [Clang 17.0.0 (clang-1700.6.3.2)])

related to #57

stack-trace

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/connector.py:1313 in │
│ _wrap_create_connection                                                                          │
│                                                                                                  │
│   1310 │   │   │   │   │   and sys.version_info >= (3, 11)                                       │
│   1311 │   │   │   │   ):                                                                        │
│   1312 │   │   │   │   │   kwargs["ssl_shutdown_timeout"] = self._ssl_shutdown_timeout           │
│ ❱ 1313 │   │   │   │   return await self._loop.create_connection(*args, **kwargs, sock=sock)     │
│   1314 │   │   except cert_errors as exc:                                                        │
│   1315 │   │   │   raise ClientConnectorCertificateError(req.connection_key, exc) from exc       │
│   1316 │   │   except ssl_errors as exc:                                                         │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/base_events.py:1198 in │
│ create_connection                                                                                │
│                                                                                                  │
│   1195 │   │   │   │   raise ValueError(                                                         │
│   1196 │   │   │   │   │   f'A Stream Socket was expected, got {sock!r}')                        │
│   1197 │   │                                                                                     │
│ ❱ 1198 │   │   transport, protocol = await self._create_connection_transport(                    │
│   1199 │   │   │   sock, protocol_factory, ssl, server_hostname,                                 │
│   1200 │   │   │   ssl_handshake_timeout=ssl_handshake_timeout,                                  │
│   1201 │   │   │   ssl_shutdown_timeout=ssl_shutdown_timeout)                                    │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/base_events.py:1231 in │
│ _create_connection_transport                                                                     │
│                                                                                                  │
│   1228 │   │   │   transport = self._make_socket_transport(sock, protocol, waiter)               │
│   1229 │   │                                                                                     │
│   1230 │   │   try:                                                                              │
│ ❱ 1231 │   │   │   await waiter                                                                  │
│   1232 │   │   except:                                                                           │
│   1233 │   │   │   transport.close()                                                             │
│   1234 │   │   │   raise                                                                         │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/sslproto.py:581 in     │
│ _on_handshake_complete                                                                           │
│                                                                                                  │
│   578 │   │   │   if handshake_exc is None:                                                      │
│   579 │   │   │   │   self._set_state(SSLProtocolState.WRAPPED)                                  │
│   580 │   │   │   else:                                                                          │
│ ❱ 581 │   │   │   │   raise handshake_exc                                                        │
│   582 │   │   │                                                                                  │
│   583 │   │   │   peercert = sslobj.getpeercert()                                                │
│   584 │   │   except Exception as exc:                                                           │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/sslproto.py:563 in     │
│ _do_handshake                                                                                    │
│                                                                                                  │
│   560 │                                                                                          │
│   561 │   def _do_handshake(self):                                                               │
│   562 │   │   try:                                                                               │
│ ❱ 563 │   │   │   self._sslobj.do_handshake()                                                    │
│   564 │   │   except SSLAgainErrors:                                                             │
│   565 │   │   │   self._process_outgoing()                                                       │
│   566 │   │   except ssl.SSLError as exc:                                                        │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/ssl.py:951 in do_handshake     │
│                                                                                                  │
│    948 │                                                                                         │
│    949 │   def do_handshake(self):                                                               │
│    950 │   │   """Start the SSL/TLS handshake."""                                                │
│ ❱  951 │   │   self._sslobj.do_handshake()                                                       │
│    952 │                                                                                         │
│    953 │   def unwrap(self):                                                                     │
│    954 │   │   """Start the SSL shutdown handshake."""                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1081)

The above exception was the direct cause of the following exception:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /Users/maboras/.local/jumpstarter/bin/jmp:7 in <module>                                          │
│                                                                                                  │
│   4 if __name__ == '__main__':                                                                   │
│   5 │   if sys.argv[0].endswith('.exe'):                                                         │
│   6 │   │   sys.argv[0] = sys.argv[0][:-4]                                                       │
│ ❱ 7 │   sys.exit(jmp())                                                                          │
│   8                                                                                              │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/click/core.py:1485 in        │
│ __call__                                                                                         │
│                                                                                                  │
│   1482 │                                                                                         │
│   1483 │   def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:                           │
│   1484 │   │   """Alias for :meth:`main`."""                                                     │
│ ❱ 1485 │   │   return self.main(*args, **kwargs)                                                 │
│   1486                                                                                           │
│   1487                                                                                           │
│   1488 class _FakeSubclassCheck(type):                                                           │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/click/core.py:1406 in main   │
│                                                                                                  │
│   1403 │   │   try:                                                                              │
│   1404 │   │   │   try:                                                                          │
│   1405 │   │   │   │   with self.make_context(prog_name, args, **extra) as ctx:                  │
│ ❱ 1406 │   │   │   │   │   rv = self.invoke(ctx)                                                 │
│   1407 │   │   │   │   │   if not standalone_mode:                                               │
│   1408 │   │   │   │   │   │   return rv                                                         │
│   1409 │   │   │   │   │   # it's not safe to `ctx.exit(rv)` here!                               │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/click/core.py:1873 in invoke │
│                                                                                                  │
│   1870 │   │   │   │   super().invoke(ctx)                                                       │
│   1871 │   │   │   │   sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)                    │
│   1872 │   │   │   │   with sub_ctx:                                                             │
│ ❱ 1873 │   │   │   │   │   return _process_result(sub_ctx.command.invoke(sub_ctx))               │
│   1874 │   │                                                                                     │
│   1875 │   │   # In chain mode we create the contexts step by step, but after the                │
│   1876 │   │   # base command has been invoked.  Because at that point we do not                 │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/click/core.py:1269 in invoke │
│                                                                                                  │
│   1266 │   │   │   echo(style(message, fg="red"), err=True)                                      │
│   1267 │   │                                                                                     │
│   1268 │   │   if self.callback is not None:                                                     │
│ ❱ 1269 │   │   │   return ctx.invoke(self.callback, **ctx.params)                                │
│   1270 │                                                                                         │
│   1271 │   def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]:      │
│   1272 │   │   """Return a list of completions for the incomplete value. Looks                   │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/click/core.py:824 in invoke  │
│                                                                                                  │
│    821 │   │                                                                                     │
│    822 │   │   with augment_usage_errors(self):                                                  │
│    823 │   │   │   with ctx:                                                                     │
│ ❱  824 │   │   │   │   return callback(*args, **kwargs)                                          │
│    825 │                                                                                         │
│    826 │   def forward(self, cmd: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any:           │
│    827 │   │   """Similar to :meth:`invoke` but fills in default keyword                         │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/jumpstarter_cli_common/oidc. │
│ py:36 in wrapper                                                                                 │
│                                                                                                  │
│    33 │   )                                                                                      │
│    34 │   @wraps(f)                                                                              │
│    35 │   def wrapper(*args, **kwds):                                                            │
│ ❱  36 │   │   return f(*args, **kwds)                                                            │
│    37 │                                                                                          │
│    38 │   return wrapper                                                                         │
│    39                                                                                            │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/jumpstarter_cli_common/confi │
│ g.py:94 in wrapper                                                                               │
│                                                                                                  │
│    91 │   │   except Exception as e:                                                             │
│    92 │   │   │   raise click.ClickException("Failed to load config: {}".format(e)) from e       │
│    93 │   │                                                                                      │
│ ❱  94 │   │   return f(*args, **kwds, config=config)                                             │
│    95 │                                                                                          │
│    96 │   return reduce(lambda w, opt: opt(w), options, wrapper)                                 │
│    97                                                                                            │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/jumpstarter_cli_common/block │
│ ing.py:8 in wrapper                                                                              │
│                                                                                                  │
│    5 def blocking(f):                                                                            │
│    6 │   @wraps(f)                                                                               │
│    7 │   def wrapper(*args, **kwargs):                                                           │
│ ❱  8 │   │   return run(f(*args, **kwargs))                                                      │
│    9 │                                                                                           │
│   10 │   return wrapper                                                                          │
│   11                                                                                             │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/runners.py:204 in run  │
│                                                                                                  │
│   201 │   │   │   "asyncio.run() cannot be called from a running event loop")                    │
│   202 │                                                                                          │
│   203 │   with Runner(debug=debug, loop_factory=loop_factory) as runner:                         │
│ ❱ 204 │   │   return runner.run(main)                                                            │
│   205                                                                                            │
│   206                                                                                            │
│   207 def _cancel_all_tasks(loop):                                                               │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/runners.py:127 in run  │
│                                                                                                  │
│   124 │   │                                                                                      │
│   125 │   │   self._interrupt_count = 0                                                          │
│   126 │   │   try:                                                                               │
│ ❱ 127 │   │   │   return self._loop.run_until_complete(task)                                     │
│   128 │   │   except exceptions.CancelledError:                                                  │
│   129 │   │   │   if self._interrupt_count > 0:                                                  │
│   130 │   │   │   │   uncancel = getattr(task, "uncancel", None)                                 │
│                                                                                                  │
│ /Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/asyncio/base_events.py:719 in  │
│ run_until_complete                                                                               │
│                                                                                                  │
│    716 │   │   if not future.done():                                                             │
│    717 │   │   │   raise RuntimeError('Event loop stopped before Future completed.')             │
│    718 │   │                                                                                     │
│ ❱  719 │   │   return future.result()                                                            │
│    720 │                                                                                         │
│    721 │   def stop(self):                                                                       │
│    722 │   │   """Stop running the event loop.                                                   │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/jumpstarter_cli/login.py:135 │
│ in login                                                                                         │
│                                                                                                  │
│   132 │   │   tokens = await oidc.password_grant(username, password)                             │
│   133 │   else:                                                                                  │
│   134 │   │   prompt = "login" if force else None                                                │
│ ❱ 135 │   │   tokens = await oidc.authorization_code_grant(callback_port=callback_port, prompt   │
│   136 │                                                                                          │
│   137 │   config.token = tokens["access_token"]                                                  │
│   138                                                                                            │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/jumpstarter_cli_common/oidc. │
│ py:86 in authorization_code_grant                                                                │
│                                                                                                  │
│    83 │   │   )                                                                                  │
│    84 │                                                                                          │
│    85 │   async def authorization_code_grant(self, callback_port: int | None = None, prompt: s   │
│ ❱  86 │   │   config = await self.configuration()                                                │
│    87 │   │                                                                                      │
│    88 │   │   # Use provided port, fall back to env var, then default to 0 (OS picks)            │
│    89 │   │   if callback_port is not None:                                                      │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/jumpstarter_cli_common/oidc. │
│ py:49 in configuration                                                                           │
│                                                                                                  │
│    46 │                                                                                          │
│    47 │   async def configuration(self):                                                         │
│    48 │   │   async with aiohttp.ClientSession() as session:                                     │
│ ❱  49 │   │   │   async with session.get(                                                        │
│    50 │   │   │   │   URL(self.issuer).joinpath(".well-known", "openid-configuration"),          │
│    51 │   │   │   │   raise_for_status=True,                                                     │
│    52 │   │   │   ) as response:                                                                 │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/client.py:1510 in    │
│ __aenter__                                                                                       │
│                                                                                                  │
│   1507 │   │   return self.__await__()                                                           │
│   1508 │                                                                                         │
│   1509 │   async def __aenter__(self) -> _RetType:                                               │
│ ❱ 1510 │   │   self._resp: _RetType = await self._coro                                           │
│   1511 │   │   return await self._resp.__aenter__()                                              │
│   1512 │                                                                                         │
│   1513 │   async def __aexit__(                                                                  │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/client.py:779 in     │
│ _request                                                                                         │
│                                                                                                  │
│    776 │   │   │   │   │   │   handler = _connect_and_send_request                               │
│    777 │   │   │   │   │                                                                         │
│    778 │   │   │   │   │   try:                                                                  │
│ ❱  779 │   │   │   │   │   │   resp = await handler(req)                                         │
│    780 │   │   │   │   │   # Client connector errors should not be retried                       │
│    781 │   │   │   │   │   except (                                                              │
│    782 │   │   │   │   │   │   ConnectionTimeoutError,                                           │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/client.py:734 in     │
│ _connect_and_send_request                                                                        │
│                                                                                                  │
│    731 │   │   │   │   │   │   # connection timeout                                              │
│    732 │   │   │   │   │   │   assert self._connector is not None                                │
│    733 │   │   │   │   │   │   try:                                                              │
│ ❱  734 │   │   │   │   │   │   │   conn = await self._connector.connect(                         │
│    735 │   │   │   │   │   │   │   │   req, traces=traces, timeout=real_timeout                  │
│    736 │   │   │   │   │   │   │   )                                                             │
│    737 │   │   │   │   │   │   except asyncio.TimeoutError as exc:                               │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/connector.py:672 in  │
│ connect                                                                                          │
│                                                                                                  │
│    669 │   │   │   │   if traces:                                                                │
│    670 │   │   │   │   │   for trace in traces:                                                  │
│    671 │   │   │   │   │   │   await trace.send_connection_create_start()                        │
│ ❱  672 │   │   │   │   proto = await self._create_connection(req, traces, timeout)               │
│    673 │   │   │   │   if traces:                                                                │
│    674 │   │   │   │   │   for trace in traces:                                                  │
│    675 │   │   │   │   │   │   await trace.send_connection_create_end()                          │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/connector.py:1239 in │
│ _create_connection                                                                               │
│                                                                                                  │
│   1236 │   │   if req.proxy:                                                                     │
│   1237 │   │   │   _, proto = await self._create_proxy_connection(req, traces, timeout)          │
│   1238 │   │   else:                                                                             │
│ ❱ 1239 │   │   │   _, proto = await self._create_direct_connection(req, traces, timeout)         │
│   1240 │   │                                                                                     │
│   1241 │   │   return proto                                                                      │
│   1242                                                                                           │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/connector.py:1611 in │
│ _create_direct_connection                                                                        │
│                                                                                                  │
│   1608 │   │   │   return transp, proto                                                          │
│   1609 │   │   else:                                                                             │
│   1610 │   │   │   assert last_exc is not None                                                   │
│ ❱ 1611 │   │   │   raise last_exc                                                                │
│   1612 │                                                                                         │
│   1613 │   async def _create_proxy_connection(                                                   │
│   1614 │   │   self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout"         │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/connector.py:1580 in │
│ _create_direct_connection                                                                        │
│                                                                                                  │
│   1577 │   │   │   )                                                                             │
│   1578 │   │   │                                                                                 │
│   1579 │   │   │   try:                                                                          │
│ ❱ 1580 │   │   │   │   transp, proto = await self._wrap_create_connection(                       │
│   1581 │   │   │   │   │   self._factory,                                                        │
│   1582 │   │   │   │   │   timeout=timeout,                                                      │
│   1583 │   │   │   │   │   ssl=sslcontext,                                                       │
│                                                                                                  │
│ /Users/maboras/.local/jumpstarter/venv/lib/python3.14/site-packages/aiohttp/connector.py:1315 in │
│ _wrap_create_connection                                                                          │
│                                                                                                  │
│   1312 │   │   │   │   │   kwargs["ssl_shutdown_timeout"] = self._ssl_shutdown_timeout           │
│   1313 │   │   │   │   return await self._loop.create_connection(*args, **kwargs, sock=sock)     │
│   1314 │   │   except cert_errors as exc:                                                        │
│ ❱ 1315 │   │   │   raise ClientConnectorCertificateError(req.connection_key, exc) from exc       │
│   1316 │   │   except ssl_errors as exc:                                                         │
│   1317 │   │   │   raise ClientConnectorSSLError(req.connection_key, exc) from exc               │
│   1318 │   │   except OSError as exc:                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ClientConnectorCertificateError: Cannot connect to host auth.redhat.com:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1081)')]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions