Skip to content

Conversation

@NickCao
Copy link
Collaborator

@NickCao NickCao commented Mar 19, 2025

Summary by CodeRabbit

  • New Features
    • Introduced configurable alternative endpoints to enhance connectivity and secure communications, enabling optimal performance even in challenging network conditions.
  • Refactor
    • Streamlined connection handling and service interactions for a more resilient user experience.
  • Chores
    • Revised dependency management to ensure up-to-date security libraries are used.
  • Tests
    • Expanded test coverage to validate the new alternative endpoint configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2025

Walkthrough

The changes add support for alternative endpoints across the Jumpstarter codebase. Functions in client modules now accept a new use_alternative_endpoints parameter and work with alternative channels when enabled. The exporter and session modules incorporate alternative endpoints into their processing flows, including secure channel creation via a new TLS helper. Additionally, configuration classes and environment utilities are updated to manage the alternative endpoints setting. The dependency on cryptography has been moved to runtime. Overall, these modifications expand endpoint connectivity options and streamline client-to-exporter communication.

Changes

Files Summary
client/client.py, client/core.py, client/grpc.py, client/lease.py Added use_alternative_endpoints parameter to client functions; updated client signatures to use stub instead of channel; introduced new SmartExporterStub class; and modified lease connection to include the alternative endpoints flag.
config/exporter.py, config/client.py, config/env.py Introduced alternative_endpoints in exporter config and use_alternative_endpoints in client config; added constant JMP_USE_ALT_ENDPOINTS for environment management; updated configuration serialization to use alias names.
exporter/exporter.py, exporter/session.py, exporter/tls.py Enabled exporter and session methods to accept and propagate alternative endpoints; updated session report, and added with_alternative_endpoints to create secure connections with self-signed TLS certificates.
common/utils.py, common/utils_test.py, config/client_config_test.py Updated launch_shell and related utility functions to support the new use_alternative_endpoints parameter; modified tests to include the additional parameter in function calls.
pyproject.toml Adjusted the cryptography dependency by moving it to the main dependencies and removing it from development dependencies.
cli/shell.py Modified the launch_shell invocation to pass the use_alternative_endpoints setting from the configuration.

Sequence Diagram(s)

sequenceDiagram
    participant Env as Environment
    participant CFP as ClientFromPath
    participant CFC as ClientFromChannel
    participant Sess as Session
    participant SC as SecureChannelCreator
    participant Stub as SmartExporterStub

    Env->>CFP: Read JMP_USE_ALT_ENDPOINTS flag
    CFP->>CFC: Pass use_alternative_endpoints flag
    CFC->>Sess: Call GetReport()
    Sess-->>CFC: Return report with alternative_endpoints
    alt use_alternative_endpoints enabled
        CFC->>SC: Create secure channels for each alternative endpoint
        SC-->>CFC: Return list of secure channels
        CFC->>Stub: Initialize SmartExporterStub with reversed channels
    else Standard flow
        CFC->>Stub: Initialize SmartExporterStub with default channel
    end
Loading

Suggested reviewers

  • mangelajo
  • bennyz

Poem

I’m a happy rabbit, swift and spry,
Hopping through code under azure sky.
New endpoints bloom like carrots so sweet,
Secure channels make my journey complete.
With TLS magic and paths so bright,
I celebrate these changes with pure delight!
🐰✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@netlify
Copy link

netlify bot commented Mar 19, 2025

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit b8dd758
🔍 Latest deploy log https://app.netlify.com/sites/jumpstarter-docs/deploys/67e16e2513e26e0008f08dcc
😎 Deploy Preview https://deploy-preview-361--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/jumpstarter/jumpstarter/exporter/tls.py (1)

33-74: Address the TODO for token generation & consider manageability of the self-signed certificate.

  1. The # FIXME: generate and check token comment signals that a critical feature (token generation/validation) is not yet implemented. Ensure proper handling before production usage.
  2. Storing private keys and certificates in code is acceptable here for ephemeral usage, though be mindful of secure distribution if used in more permanent scenarios.
    Otherwise, this function effectively configures TLS with SAN for each endpoint.

Do you want me to propose a minimal token-based authentication flow or file a follow-up ticket?

packages/jumpstarter/jumpstarter/exporter/session.py (1)

100-100: Good usage of _alternative_endpoints in GetReport.

Exposing alternative_endpoints in the response provides a cohesive integration for clients. As a follow-up, confirm whether clients expect these endpoints immediately or only after a successful handshake.

packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py (1)

32-32: Consider ignoring or excluding auto-generated files from lint checks.

Ruff flags this line for exceeding the max line length. Typically, large descriptor strings in generated protobuf code are best handled by ignoring the file or adjusting the generator’s formatting options.

🧰 Tools
🪛 Ruff (0.8.2)

32-32: Line too long (6808 > 120)

(E501)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d149734 and 018ba1b.

📒 Files selected for processing (7)
  • packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py (2 hunks)
  • packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py (2 hunks)
  • packages/jumpstarter/jumpstarter/client/client.py (3 hunks)
  • packages/jumpstarter/jumpstarter/config/exporter.py (3 hunks)
  • packages/jumpstarter/jumpstarter/exporter/exporter.py (2 hunks)
  • packages/jumpstarter/jumpstarter/exporter/session.py (4 hunks)
  • packages/jumpstarter/jumpstarter/exporter/tls.py (1 hunks)
🧰 Additional context used
🧬 Code Definitions (3)
packages/jumpstarter/jumpstarter/exporter/exporter.py (3)
packages/jumpstarter/jumpstarter/config/exporter.py (1) (1)
  • serve_unix_async (143-151)
packages/jumpstarter/jumpstarter/exporter/session.py (1) (1)
  • serve_unix_async (77-80)
packages/jumpstarter/jumpstarter/client/lease.py (1) (1)
  • serve_unix_async (155-157)
packages/jumpstarter/jumpstarter/client/client.py (2)
packages/jumpstarter/jumpstarter/exporter/session.py (1) (1)
  • GetReport (91-101)
packages/jumpstarter/jumpstarter/config/client.py (1) (1)
  • channel (54-60)
packages/jumpstarter/jumpstarter/exporter/session.py (3)
packages/jumpstarter/jumpstarter/exporter/tls.py (1) (1)
  • with_alternative_endpoints (33-74)
packages/jumpstarter/jumpstarter/config/exporter.py (2) (2)
  • serve_unix_async (143-151)
  • serve_unix (154-157)
packages/jumpstarter/jumpstarter/client/lease.py (2) (2)
  • serve_unix_async (155-157)
  • serve_unix (172-174)
🪛 GitHub Actions: Lint
packages/jumpstarter/jumpstarter/client/client.py

[error] 1-1: Ruff: Import block is un-sorted or un-formatted.

🪛 Ruff (0.8.2)
packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py

32-32: Line too long (6808 > 120)

(E501)

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: e2e
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter Dockerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-utils Dockerfile.utils)
🔇 Additional comments (13)
packages/jumpstarter-cli-admin/jumpstarter_cli_admin/get_test.py (2)

767-767: Code formatting looks good

The addition of a blank line before the test function improves readability by creating better visual separation between test cases.


1145-1145: Code formatting looks good

The addition of a blank line before the test function creates better visual separation between test cases, improving readability.

packages/jumpstarter/jumpstarter/exporter/exporter.py (2)

28-28: Well-implemented attribute addition

Good use of field(default_factory=list) to avoid the mutable default argument issue. This new attribute will store alternative endpoints for the exporter.


53-53: Properly integrates the alternative_endpoints parameter

The session method now correctly passes the alternative_endpoints to serve_unix_async, enabling the exporter to use these endpoints during session creation.

packages/jumpstarter/jumpstarter/config/exporter.py (3)

5-5: Appropriate import addition

The import of List from typing is necessary for the new alternative_endpoints field type annotation.


85-86: Well-implemented attribute addition

Good implementation of the alternative_endpoints attribute with proper type annotation and default_factory to avoid mutable default issues.


174-174: Correctly passes alternative_endpoints to Exporter

The serve method now properly passes the alternative_endpoints to the Exporter constructor, ensuring configuration is properly propagated.

packages/jumpstarter/jumpstarter/client/client.py (1)

30-30: Good parameter addition with sensible default

Adding the optional use_alternative_endpoints parameter with a default of True maintains backward compatibility while enabling the new feature.

packages/jumpstarter/jumpstarter/exporter/tls.py (1)

12-31: Great job on parse_endpoint.

The approach using rpartition(':') and handling IPv6 bracket stripping is clear and robust. As a minor caution, consider confirming that host is non-empty to avoid acceptance of malformed endpoints (e.g., :8080).

packages/jumpstarter/jumpstarter/exporter/session.py (3)

18-19: Thorough integration of alternative endpoints.

  • _alternative_endpoints is introduced and properly assigned if the parameter is non-null.
  • The with_alternative_endpoints call ensures each endpoint is secured via TLS.

However, be cautious when combining insecure and secure ports on the same server. Ensure the runtime usage scenario is well-understood and tested.

Also applies to: 57-65


83-85: Synchronous wrapper usage is straightforward.

Wrapping serve_unix_async with an optional alternative_endpoints parameter ensures consistent usage across both async and sync methods. This is helpful for clients that require synchronous flows.


77-79:

Details

❓ Verification inconclusive

Confirm handling of Unix socket endpoints.

Serving a Unix socket while also binding alternative TLS endpoints is logical, but consider verifying or documenting that an empty list of alternative endpoints won't produce unexpected behavior.


🏁 Script executed:

#!/bin/bash
# Description: Check if the code gracefully handles empty alternative_endpoints.
grep -A5 "async def serve_unix_async" packages/jumpstarter/jumpstarter/exporter/session.py

Length of output: 354


Confirm behavior of empty alternative_endpoints in Unix socket serving

After reviewing the snippet, it's clear that the serve_unix_async method passes the alternative_endpoints parameter directly to serve_ports_async without any additional guard or normalization when it’s an empty list. To avoid potential surprises at runtime, please verify that serve_ports_async is designed to handle an empty list (as opposed to None) correctly. If the existing behavior is intentional, consider adding inline documentation or a docstring note clarifying that an empty list is a supported and benign input.

  • Location: packages/jumpstarter/jumpstarter/exporter/session.py (lines 77–79)
packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2.py (1)

75-112: New fields for alternative endpoint handling look good.

The addition of _ENDPOINT, references to alternative_endpoints in GetReportResponse, and expansions for StreamDriverCall align with the rest of the codebase. Implementation is consistent with the session changes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/jumpstarter/pyproject.toml (2)

19-19: Dependency Addition: Runtime Cryptography Inclusion
The addition of "cryptography>=43.0.3" to the runtime dependencies (line 19) is clear and aligns with the objective to support new secure channel capabilities. Please verify that the specified minimum version meets your security and feature requirements.


23-32: Dev Dependency Cleanup: Removal of Cryptography
The removal of "cryptography>=43.0.3" from the development dependency group helps to avoid redundancy and ensures that the package is only installed for runtime. Confirm that no development tooling or tests depend on cryptography being explicitly listed in the dev section.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97cc6b5 and 4fb137c.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • packages/jumpstarter/pyproject.toml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: pytest-matrix (3.11)
  • GitHub Check: e2e
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter Dockerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)

@NickCao NickCao force-pushed the direct branch 2 times, most recently from be45de3 to c7e12e7 Compare March 21, 2025 14:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/jumpstarter/jumpstarter/client/client.py (1)

38-52: ⚠️ Potential issue

Add error handling for failed connection attempts

The code attempts to connect to alternative endpoints without proper error handling. If connection to an endpoint fails, it will silently continue to the next one.

Apply this diff to log failed connection attempts:

            if endpoint.certificate:
+               try:
                    channels.append(
                        grpc.aio.secure_channel(
                            endpoint.endpoint,
                            grpc.ssl_channel_credentials(
                                root_certificates=endpoint.certificate.encode(),
                                private_key=endpoint.client_private_key.encode(),
                                certificate_chain=endpoint.client_certificate.encode(),
                            ),
                        )
                    )
+               except Exception as e:
+                   # Log failed connection attempt
+                   import logging
+                   logger = logging.getLogger(__name__)
+                   logger.debug(f"Failed to connect to alternative endpoint {endpoint.endpoint}: {e}")
🧹 Nitpick comments (3)
packages/jumpstarter/jumpstarter/client/client.py (1)

36-36: Inefficient stub creation

A temporary SmartExporterStub is created just to call GetReport, then discarded. This is inefficient as it creates stub objects that are immediately thrown away.

Consider refactoring to reuse the stub:

-    response = await SmartExporterStub([channel]).GetReport(empty_pb2.Empty())
+    temp_stub = SmartExporterStub([channel])
+    response = await temp_stub.GetReport(empty_pb2.Empty())
packages/jumpstarter/jumpstarter/client/grpc.py (1)

259-279: Improve stub method invocation performance

The current implementation of __getattr__ checks the state of every channel on each method call, which could cause performance issues when dealing with many channels or frequent method calls.

Consider implementing a caching mechanism for ready channels to avoid repeated connection attempts:

@dataclass(frozen=True, slots=True)
class SmartExporterStub:
    channels: InitVar[list[Channel]]

    __stubs: dict[Channel, Any] = field(init=False, default_factory=OrderedDict)
+   __ready_channel: Any = field(init=False, default=None)
+   __ready_stub: Any = field(init=False, default=None)

    def __post_init__(self, channels):
        for channel in channels:
            stub = SimpleNamespace()
            jumpstarter_pb2_grpc.ExporterServiceStub.__init__(stub, channel)
            router_pb2_grpc.RouterServiceStub.__init__(stub, channel)
            self.__stubs[channel] = stub

    def __getattr__(self, name):
+       # First check if we have a cached ready channel
+       if self.__ready_channel is not None:
+           if self.__ready_channel.get_state(try_to_connect=False) == ChannelConnectivity.READY:
+               return getattr(self.__ready_stub, name)
+           # Reset cache if no longer ready
+           self.__ready_channel = None
+           self.__ready_stub = None
+
        for channel, stub in self.__stubs.items():
            # find the first channel that's ready
            if channel.get_state(try_to_connect=True) == ChannelConnectivity.READY:
+               # Cache the ready channel and stub
+               self.__ready_channel = channel
+               self.__ready_stub = stub
                return getattr(stub, name)
        # or fallback to the last channel (via router)
        return getattr(next(reversed(self.__stubs.values())), name)
packages/jumpstarter/jumpstarter/client/core.py (1)

64-64: Improve type annotation for stub

The current type annotation for stub is Any, which reduces type safety and IDE auto-completion capabilities.

Use a more specific type annotation to improve code quality:

-    stub: Any
+    stub: 'SmartExporterStub'

Also, consider adding an import statement:

+from jumpstarter.client.grpc import SmartExporterStub
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be45de3 and c7e12e7.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • buf.gen.yaml (1 hunks)
  • packages/jumpstarter/jumpstarter/client/client.py (3 hunks)
  • packages/jumpstarter/jumpstarter/client/core.py (7 hunks)
  • packages/jumpstarter/jumpstarter/client/grpc.py (2 hunks)
  • packages/jumpstarter/jumpstarter/config/exporter.py (3 hunks)
  • packages/jumpstarter/jumpstarter/exporter/exporter.py (2 hunks)
  • packages/jumpstarter/jumpstarter/exporter/session.py (4 hunks)
  • packages/jumpstarter/jumpstarter/exporter/tls.py (1 hunks)
  • packages/jumpstarter/pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/jumpstarter/jumpstarter/config/exporter.py
  • packages/jumpstarter/pyproject.toml
  • packages/jumpstarter/jumpstarter/exporter/tls.py
  • buf.gen.yaml
  • packages/jumpstarter/jumpstarter/exporter/exporter.py
  • packages/jumpstarter/jumpstarter/exporter/session.py
🧰 Additional context used
🧬 Code Definitions (2)
packages/jumpstarter/jumpstarter/client/grpc.py (2)
packages/jumpstarter/jumpstarter/config/client.py (1) (1)
  • channel (55-61)
packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py (1) (1)
  • ExporterServiceStub (489-524)
packages/jumpstarter/jumpstarter/client/core.py (2)
packages/jumpstarter/jumpstarter/exporter/session.py (3) (3)
  • DriverCall (103-105)
  • StreamingDriverCall (107-110)
  • Stream (112-125)
packages/jumpstarter/jumpstarter/driver/base.py (3) (3)
  • DriverCall (87-112)
  • StreamingDriverCall (114-142)
  • Stream (145-169)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.11)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: e2e
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-utils Dockerfile.utils)
🔇 Additional comments (2)
packages/jumpstarter/jumpstarter/client/client.py (1)

53-53:

Details

❓ Verification inconclusive

Clarify channel prioritization strategy

The code reverses the list of channels when creating the final stub, which means alternative endpoints are prioritized over the primary channel. This may not be the intended behavior, as typically the primary channel would be the preferred one.


🏁 Script executed:

#!/bin/bash
# Search for comments or documentation about channel prioritization strategy
rg -A 3 -B 3 "endpoint.*priorit|priorit.*endpoint|channel.*order|order.*channel" --type py

Length of output: 92


Channel Prioritization: Clarification Needed

It appears that reversing the channel list causes the last defined channel to be used as the primary endpoint. Since our search didn’t reveal any documentation or inline comments that specify this behavior, please verify if this inversion is intentional. If the primary channel should indeed be favored, consider either removing the reversal or adding explicit comments that justify this ordering.

  • File: packages/jumpstarter/jumpstarter/client/client.py, line 53
  • Code: stub = SmartExporterStub(list(reversed(channels)))
packages/jumpstarter/jumpstarter/client/core.py (1)

91-91: Updated stub usage looks good

The changes from using individual service stubs to using the consolidated SmartExporterStub are consistent and correctly implemented across all methods.

Also applies to: 115-115, 130-134, 144-148, 162-163

@NickCao NickCao changed the title [WIP] direct client to exporter connectivity direct client to exporter connectivity Mar 21, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7e12e7 and aca7e3d.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • packages/jumpstarter-cli-client/jumpstarter_cli_client/client_shell.py (1 hunks)
  • packages/jumpstarter/jumpstarter/client/client.py (3 hunks)
  • packages/jumpstarter/jumpstarter/client/core.py (7 hunks)
  • packages/jumpstarter/jumpstarter/client/grpc.py (2 hunks)
  • packages/jumpstarter/jumpstarter/client/lease.py (2 hunks)
  • packages/jumpstarter/jumpstarter/common/utils.py (4 hunks)
  • packages/jumpstarter/jumpstarter/config/client.py (2 hunks)
  • packages/jumpstarter/jumpstarter/config/env.py (1 hunks)
  • packages/jumpstarter/jumpstarter/config/exporter.py (3 hunks)
  • packages/jumpstarter/jumpstarter/exporter/exporter.py (2 hunks)
  • packages/jumpstarter/jumpstarter/exporter/session.py (4 hunks)
  • packages/jumpstarter/jumpstarter/exporter/tls.py (1 hunks)
  • packages/jumpstarter/pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/jumpstarter/jumpstarter/config/env.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/jumpstarter/pyproject.toml
  • packages/jumpstarter/jumpstarter/config/exporter.py
  • packages/jumpstarter/jumpstarter/exporter/exporter.py
  • packages/jumpstarter/jumpstarter/client/core.py
  • packages/jumpstarter/jumpstarter/exporter/tls.py
  • packages/jumpstarter/jumpstarter/exporter/session.py
🧰 Additional context used
🧬 Code Definitions (3)
packages/jumpstarter-cli-client/jumpstarter_cli_client/client_shell.py (1)
packages/jumpstarter/jumpstarter/common/utils.py (1) (1)
  • launch_shell (92-120)
packages/jumpstarter/jumpstarter/client/grpc.py (2)
packages/jumpstarter/jumpstarter/config/client.py (1) (1)
  • channel (57-63)
packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py (1) (1)
  • ExporterServiceStub (489-524)
packages/jumpstarter/jumpstarter/client/client.py (2)
packages/jumpstarter/jumpstarter/client/grpc.py (1) (1)
  • SmartExporterStub (260-278)
packages/jumpstarter/jumpstarter/client/base.py (1) (1)
  • DriverClient (19-99)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: e2e
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-utils Dockerfile.utils)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter Dockerfile)
🔇 Additional comments (17)
packages/jumpstarter-cli-client/jumpstarter_cli_client/client_shell.py (1)

25-27: Pass new parameter to launch_shell successfully
The addition of config.useAlternativeEndpoints aligns with the updated launch_shell function signature which expects a boolean for use_alternative_endpoints. This enables proper handling of alternative endpoints without breaking existing code flow.

packages/jumpstarter/jumpstarter/config/client.py (2)

53-54: New useAlternativeEndpoints attribute
Introducing this boolean attribute with a default of False preserves backward compatibility. A docstring or comment clarifying its usage could improve maintainability.


177-177: Passing use_alternative_endpoints to the Lease constructor
Forwarding the new configuration flag ensures consistency. Good job tying this back to the new usage in Lease.

packages/jumpstarter/jumpstarter/client/lease.py (2)

44-44: Introduce use_alternative_endpoints in Lease
The default value of False prevents unintended behavior while enabling future toggling for alternative endpoints.


188-195: Passing the flag in connect_async
Propagating use_alternative_endpoints to client_from_path integrates well with the rest of the configuration. This helps unify the lease handling logic with the client's ability to use alternative endpoints.

packages/jumpstarter/jumpstarter/client/grpc.py (4)

3-7: Imports for multiple stubs and connectivity checks
These imports (e.g., OrderedDict, SimpleNamespace, ChannelConnectivity, etc.) properly support the new SmartExporterStub class and its multi-channel logic.

Also applies to: 11-11, 13-13


259-264: Introducing SmartExporterStub class
Using a frozen dataclass with slots=True is a memory-efficient approach. Documenting its responsibilities or adding a docstring could improve clarity for other contributors.


265-270: Uncommon initialization pattern for stubs
This direct invocation of the stubs’ __init__ on a SimpleNamespace was previously flagged. It's potentially fragile if stub implementations change. Adopting the conventional approach (e.g., assigning actual ExporterServiceStub(channel) instances to attributes) is recommended.


272-278: Fallback logic for no ready channels
As noted in the past review, consider more robust error handling or logging when no channels are ready. Documenting the fallback approach helps clarify how the code behaves in unexpected situations.

packages/jumpstarter/jumpstarter/client/client.py (5)

10-10: Good choice using SmartExporterStub

The switch from ExporterServiceStub to SmartExporterStub enables the multi-channel functionality needed for alternative endpoints.


43-44: Architecture change to support multiple channels

The code now initializes SmartExporterStub with a list containing just the original channel, setting up the foundation for alternative endpoint support.


45-59: Address TODOs for error handling and token authentication

The implementation for alternative endpoint connection looks good, but you should address the error handling when connecting to alternative endpoints.

Consider implementing error handling as previously suggested:

        for endpoint in response.alternative_endpoints:
            if endpoint.certificate:
+               try:
                    channels.append(
                        grpc.aio.secure_channel(
                            endpoint.endpoint,
                            grpc.ssl_channel_credentials(
                                root_certificates=endpoint.certificate.encode(),
                                private_key=endpoint.client_private_key.encode(),
                                certificate_chain=endpoint.client_certificate.encode(),
                            ),
                        )
                    )
+               except Exception as e:
+                   # Log failed connection attempt
+                   logger.debug(f"Failed to connect to alternative endpoint {endpoint.endpoint}: {e}")

Also add the logger import at the top of the file:

import logging

logger = logging.getLogger(__name__)

60-60:

Details

❓ Verification inconclusive

Consider the channel priority logic

You're reversing the channels list before passing to SmartExporterStub, which prioritizes alternative endpoints over the original channel. Ensure this is the intended behavior.


🏁 Script executed:

#!/bin/bash
# Find where SmartExporterStub is used to understand channel prioritization
rg -A 2 "SmartExporterStub" --type py

Length of output: 1052


Channel Priority Logic: Confirm Intentional Reversal

The code currently initializes SmartExporterStub with a reversed list of channels (i.e. list(reversed(channels))), which means the endpoint originally positioned last in the channels list becomes the primary one. We also see another usage where a single channel is passed directly. Please confirm that this inversion is deliberate—specifically, that you intend for alternative endpoints (from the reversed list) to take priority over the original channel. If so, consider adding a clarifying comment to highlight this behavior.


80-80:

Details

❓ Verification inconclusive

API change from channel to stub

Changing the parameter from channel to stub is a significant API change. Ensure all client implementations are updated to work with this new parameter.


🏁 Script executed:

#!/bin/bash
# Find all client implementations that might be using the channel parameter
ast-grep --pattern "client = client_class(\n  $$$\n  channel=$_,\n  $$$\n)"

Length of output: 94


I'm not getting a definitive verification from the ast-grep output. It looks like the command didn't return clear results regarding usages of the legacy channel parameter. To be sure that all client implementations have been updated to use the new stub parameter, please run an additional search across the repository. For example, you can try the following shell script to search for any remaining occurrences of "channel=":

#!/bin/bash
# Search for any remaining occurrences of the legacy "channel=" usage in the repository
rg --ignore-case "channel=" -n .

If this search produces any hits, please review them and update the corresponding client implementations to use stub instead. If no results appear, then the API change appears to have been fully applied.

Action: Please verify that no implementations still reference channel.
File: packages/jumpstarter/jumpstarter/client/client.py (line 80) already uses stub=stub,.
Next Step: Run the above script and manually confirm no legacy usages remain.

packages/jumpstarter/jumpstarter/common/utils.py (3)

10-10: Good addition of environment variable constant

Adding the JMP_USE_ALTERNATIVE_ENDPOINTS constant is a clean approach to propagate the setting through the environment.


55-56: Good environment variable handling

The conversion from string environment variable to boolean is clean and maintainable.


116-116: Good environment variable setting

The environment variable setting is consistent with how the value is read in env_async().

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/jumpstarter/jumpstarter/config/client_config_test.py (1)

210-210: LGTM: New configuration field for alternative endpoints added consistently.

The useAlternativeEndpoints: false field has been consistently added to all relevant test configurations, which matches the behavior described in the PR objectives for direct client-to-exporter connectivity.

Consider adding a specific test case that verifies the behavior when this field is set to true to ensure that both states of this feature are properly tested.

Also applies to: 247-247, 282-282

packages/jumpstarter/jumpstarter/common/utils_test.py (1)

8-14: LGTM: Updated function calls with new parameter.

The launch_shell function calls have been properly updated to include the new use_alternative_endpoints parameter, which aligns with the function signature change in the implementation file.

Consider adding a test case with use_alternative_endpoints=True to verify that the alternative endpoints functionality works correctly when enabled.

Also applies to: 18-24

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aca7e3d and 475a5c1.

📒 Files selected for processing (2)
  • packages/jumpstarter/jumpstarter/common/utils_test.py (1 hunks)
  • packages/jumpstarter/jumpstarter/config/client_config_test.py (3 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
packages/jumpstarter/jumpstarter/common/utils_test.py (1)
packages/jumpstarter/jumpstarter/common/utils.py (1) (1)
  • launch_shell (92-120)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: pytest-matrix (3.11)
  • GitHub Check: e2e
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter Dockerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-utils Dockerfile.utils)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
packages/jumpstarter/jumpstarter/config/client.py (1)

53-54: Consider using field_alias convention for better Python/JSON compatibility

The addition of use_alternative_endpoints with a camelCase alias is good, but consider following the pattern used in the jumpstarter_kubernetes package where snake_case is used in Python and camelCase in output YAML/JSON.

-    use_alternative_endpoints: bool = Field(alias="useAlternativeEndpoints", default=False)
+    use_alternative_endpoints: bool = Field(
+        alias="useAlternativeEndpoints", 
+        description="Whether to use alternative endpoints for direct client connection",
+        default=False
+    )

This approach maintains Python standards while ensuring proper serialization to YAML/JSON. Adding a description would also improve code documentation.

packages/jumpstarter/jumpstarter/exporter/tls.py (5)

13-15: Add docstring and consider parameterizing RSA key parameters.

The function lacks documentation explaining its purpose, parameters, and return value. Additionally, consider parameterizing the RSA key parameters (key size, public exponent) to allow for flexibility.

 def with_alternative_endpoints(server, endpoints: list[str]):
+    """Set up mutual TLS authentication for alternative endpoints.
+    
+    Args:
+        server: The gRPC server instance to add secure ports to
+        endpoints: A list of endpoint strings to secure
+        
+    Returns:
+        List of Endpoint protocol buffer messages containing the endpoint information
+        and authentication credentials
+    """
+    # RSA parameters
+    KEY_SIZE = 2048
+    PUBLIC_EXPONENT = 65537
+    
-    key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
-    client_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
+    key = rsa.generate_private_key(public_exponent=PUBLIC_EXPONENT, key_size=KEY_SIZE, backend=default_backend())
+    client_key = rsa.generate_private_key(public_exponent=PUBLIC_EXPONENT, key_size=KEY_SIZE, backend=default_backend())

17-37: Document certificate generation and consider adding SAN to client certificate.

The certificate generation process should be documented to explain the reasoning behind using self-signed certificates. Additionally, consider adding a Subject Alternative Name to the client certificate for consistency with the server certificate.

+    # Generate self-signed server certificate
     crt = (
         x509.CertificateBuilder()
         .subject_name(x509.Name([]))
         .issuer_name(x509.Name([]))
         .public_key(key.public_key())
         .serial_number(x509.random_serial_number())
         .not_valid_before(datetime.now())
         .not_valid_after(datetime.now() + timedelta(days=365))
         .add_extension(x509.SubjectAlternativeName([x509.DNSName(SAN)]), critical=False)
         .sign(private_key=key, algorithm=hashes.SHA256(), backend=default_backend())
     )
+    
+    # Generate self-signed client certificate
     client_crt = (
         x509.CertificateBuilder()
         .subject_name(x509.Name([]))
         .issuer_name(x509.Name([]))
         .public_key(client_key.public_key())
         .serial_number(x509.random_serial_number())
         .not_valid_before(datetime.now())
         .not_valid_after(datetime.now() + timedelta(days=365))
+        .add_extension(x509.SubjectAlternativeName([x509.DNSName(SAN)]), critical=False)
         .sign(private_key=client_key, algorithm=hashes.SHA256(), backend=default_backend())
     )

39-51: Document why keys are not encrypted.

The code is serializing private keys without encryption. While this might be acceptable for ephemeral keys used in-memory, it's worth documenting the reasoning to clarify the security implications.

+    # Serialize server certificate and key
     pem_crt = crt.public_bytes(serialization.Encoding.PEM)
     pem_key = key.private_bytes(
         encoding=serialization.Encoding.PEM,
         format=serialization.PrivateFormat.TraditionalOpenSSL,
+        # Keys are not encrypted as they're only used in-memory for the duration of the connection
         encryption_algorithm=serialization.NoEncryption(),
     )

+    # Serialize client certificate and key
     pem_client_crt = client_crt.public_bytes(serialization.Encoding.PEM)
     pem_client_key = client_key.private_bytes(
         encoding=serialization.Encoding.PEM,
         format=serialization.PrivateFormat.TraditionalOpenSSL,
+        # Keys are not encrypted as they're only used in-memory for the duration of the connection
         encryption_algorithm=serialization.NoEncryption(),
     )

53-55: Add comment explaining mutual TLS setup.

This section sets up mutual TLS (mTLS) authentication requiring client certificate verification. Adding a comment would clarify the security model being implemented.

+    # Set up mutual TLS authentication requiring client certificate verification
     server_credentials = grpc.ssl_server_credentials(
         [(pem_key, pem_crt)], root_certificates=pem_client_crt, require_client_auth=True
     )

57-68: Add error handling and consider logging.

The code adds secure ports and builds endpoint information without any error handling. Consider adding error handling for potential failures in adding secure ports to the server.

     endpoints_pb = []
     for endpoint in endpoints:
-        server.add_secure_port(endpoint, server_credentials)
+        try:
+            port = server.add_secure_port(endpoint, server_credentials)
+            if port == 0:
+                # Port 0 indicates failure in some gRPC implementations
+                print(f"Warning: Failed to add secure port for endpoint {endpoint}")
+        except Exception as e:
+            print(f"Error adding secure port for endpoint {endpoint}: {e}")
+            continue
         endpoints_pb.append(
             jumpstarter_pb2.Endpoint(
                 endpoint=endpoint,
                 certificate=pem_crt,
                 client_certificate=pem_client_crt,
                 client_private_key=pem_client_key,
             ),
         )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 475a5c1 and 1a4725d.

📒 Files selected for processing (6)
  • packages/jumpstarter-cli-client/jumpstarter_cli_client/client_shell.py (1 hunks)
  • packages/jumpstarter/jumpstarter/client/client.py (3 hunks)
  • packages/jumpstarter/jumpstarter/common/utils.py (4 hunks)
  • packages/jumpstarter/jumpstarter/config/client.py (3 hunks)
  • packages/jumpstarter/jumpstarter/config/env.py (1 hunks)
  • packages/jumpstarter/jumpstarter/exporter/tls.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/jumpstarter/jumpstarter/config/env.py
  • packages/jumpstarter-cli-client/jumpstarter_cli_client/client_shell.py
  • packages/jumpstarter/jumpstarter/common/utils.py
🧰 Additional context used
🧬 Code Definitions (2)
packages/jumpstarter/jumpstarter/config/client.py (3)
packages/jumpstarter/jumpstarter/config/exporter.py (2) (2)
  • dump_yaml (122-123)
  • dump_yaml (189-190)
packages/jumpstarter/jumpstarter/client/grpc.py (3) (3)
  • dump_yaml (102-103)
  • dump_yaml (120-121)
  • dump_yaml (138-139)
packages/jumpstarter-kubernetes/jumpstarter_kubernetes/json.py (1) (1)
  • dump_yaml (11-12)
packages/jumpstarter/jumpstarter/client/client.py (3)
packages/jumpstarter/jumpstarter/client/grpc.py (1) (1)
  • SmartExporterStub (260-278)
packages/jumpstarter/jumpstarter/client/base.py (1) (1)
  • DriverClient (19-99)
packages/jumpstarter/jumpstarter/exporter/session.py (1) (1)
  • GetReport (91-101)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.11)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-utils Dockerfile.utils)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter Dockerfile)
🔇 Additional comments (12)
packages/jumpstarter/jumpstarter/config/client.py (3)

177-177: Good handling of new parameter in lease_async

The parameter is correctly passed to the Lease constructor from the client config.


241-249: Correct use of by_alias parameter in model_dump

The addition of by_alias=True ensures the field will be serialized using its camelCase alias as specified in the Field definition, which is consistent with the formatting standards across the codebase.


254-261: Consistent implementation of by_alias in dump_yaml

The change to include by_alias=True in dump_yaml is aligned with other similar methods in the codebase, maintaining consistency in YAML serialization.

packages/jumpstarter/jumpstarter/client/client.py (8)

10-10: Smart refactoring of exporter stub import

The import now uses SmartExporterStub which handles multiple gRPC channels, supporting the alternative endpoints feature.


13-13: Import of SAN (Subject Alternative Name) for TLS configuration

The import of SAN from the TLS module is necessary for secure connections to alternative endpoints.


17-24: Properly extended function signature for alternative endpoints support

Good addition of the optional use_alternative_endpoints parameter with a default value of False, maintaining backward compatibility.


28-28: Consistent propagation of the use_alternative_endpoints parameter

Correctly passing the parameter from client_from_path to client_from_channel.


37-37: Parameter consistency in client_from_channel function

The use_alternative_endpoints parameter is consistently added with the same default value.


44-45: Improved stub initialization using SmartExporterStub

The code now uses the more flexible SmartExporterStub that can manage multiple channels, which is essential for the alternative endpoints feature.


82-82: Updated client constructor parameter from channel to stub

The change from passing channel to passing stub to the client constructor correctly adapts to the new multi-channel architecture.


46-62:

Details

✅ Verification successful

Implement alternative endpoints connection

The implementation correctly processes alternative endpoints when enabled, creating secure channels with proper TLS configuration.

Let's verify that the secure channel configuration is correct:


🏁 Script executed:

#!/bin/bash
# Description: Check the format and structure of SAN (Subject Alternative Name) constant in the TLS module 
# to ensure it's properly used in the SSL target name override option.

# Find the definition of SAN in the codebase
rg -A 2 "SAN\s*=" --type py

Length of output: 196


Secure Channel Alternative Endpoints Verified

The TLS configuration for alternative endpoints is correctly implemented. Verification confirms that the SAN constant, used in the grpc.ssl_target_name_override option, is defined in packages/jumpstarter/jumpstarter/exporter/tls.py as "localhost". This aligns with the intended configuration for secure channel setup.

  • File reviewed: packages/jumpstarter/jumpstarter/client/client.py (Lines 46–62)
  • Key detail: SAN is defined as "localhost" in packages/jumpstarter/jumpstarter/exporter/tls.py

No further changes are needed.

packages/jumpstarter/jumpstarter/exporter/tls.py (1)

1-11: Add documentation and improve constants.

The file lacks descriptive documentation for its purpose and usage. Consider adding a module-level docstring that explains what this module does and why it's needed (peer-to-peer mTLS).

Additionally, the constant SAN is hardcoded as "localhost". Consider making this configurable or at least adding a comment explaining why it's set to "localhost".

 from jumpstarter_protocol import jumpstarter_pb2

+# Certificate Subject Alternative Name
 SAN = "localhost"

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
packages/jumpstarter/jumpstarter/exporter/tls.py (5)

10-10: Document the purpose of SAN constant

The SAN constant is used for the Subject Alternative Name in certificates, but its purpose and why "localhost" is used isn't immediately clear from the code context.

-SAN = "localhost"
+# Using a fixed SAN of "localhost" since we're skipping SAN verification on the client side
+# with certificate pinning for security
+SAN = "localhost"

32-42: Extract certificate validity period as a constant

The certificate validity period is hardcoded to 365 days. Consider making this a named constant for better maintainability.

+# Certificate validity period
+CERT_VALIDITY_DAYS = 365
+
     crt = (
         x509.CertificateBuilder()
         .subject_name(x509.Name([]))
         .issuer_name(x509.Name([]))
         .public_key(key.public_key())
         .serial_number(x509.random_serial_number())
         .not_valid_before(datetime.now())
-        .not_valid_after(datetime.now() + timedelta(days=365))
+        .not_valid_after(datetime.now() + timedelta(days=CERT_VALIDITY_DAYS))
         .add_extension(x509.SubjectAlternativeName([x509.DNSName(SAN)]), critical=False)
         .sign(private_key=key, algorithm=hashes.SHA256(), backend=default_backend())
     )

43-52: Consider adding SAN to client certificate

The server certificate has a Subject Alternative Name (SAN) extension, but the client certificate doesn't. While the client certificate might not need hostname verification, adding a SAN for consistency might be beneficial.

     client_crt = (
         x509.CertificateBuilder()
         .subject_name(x509.Name([]))
         .issuer_name(x509.Name([]))
         .public_key(client_key.public_key())
         .serial_number(x509.random_serial_number())
         .not_valid_before(datetime.now())
-        .not_valid_after(datetime.now() + timedelta(days=365))
+        .not_valid_after(datetime.now() + timedelta(days=CERT_VALIDITY_DAYS))
+        .add_extension(x509.SubjectAlternativeName([x509.DNSName(SAN)]), critical=False)
         .sign(private_key=client_key, algorithm=hashes.SHA256(), backend=default_backend())
     )

54-66: Document security implications of NoEncryption for private keys

The private keys are serialized without encryption. While this might be appropriate for ephemeral keys used in a controlled environment, it's worth documenting this design decision.

     pem_key = key.private_bytes(
         encoding=serialization.Encoding.PEM,
         format=serialization.PrivateFormat.TraditionalOpenSSL,
+        # No encryption is used as these keys are ephemeral and only used for the duration of a lease
         encryption_algorithm=serialization.NoEncryption(),
     )

84-84: Consider adding basic error handling

The function assumes all operations succeed but doesn't handle potential failures in certificate generation or adding secure ports. Consider adding try-except blocks for robustness.

-    return endpoints_pb
+    try:
+        return endpoints_pb
+    except Exception as e:
+        # Log the error and/or clean up any partially created resources
+        raise RuntimeError(f"Failed to set up alternative endpoints: {e}") from e
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a4725d and 872af90.

📒 Files selected for processing (1)
  • packages/jumpstarter/jumpstarter/exporter/tls.py (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: pytest-matrix (3.11)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: e2e
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-utils Dockerfile.utils)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter Dockerfile)
🔇 Additional comments (4)
packages/jumpstarter/jumpstarter/exporter/tls.py (4)

13-27: Good documentation of mTLS implementation and rationale

The detailed docstring clearly explains:

  1. The purpose of alternative endpoints
  2. When they're useful (network constraints)
  3. The security model using mTLS
  4. The connection flow and fallback mechanism

This helps maintainers understand the security architecture and design decisions.


29-30: Extract cryptographic parameters as constants

The RSA key generation parameters are hardcoded. For better maintainability and to follow the suggestion in past reviews, consider extracting these values as named constants.

+# RSA key generation parameters
+RSA_KEY_SIZE = 2048
+RSA_PUBLIC_EXPONENT = 65537
+
-    key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
-    client_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
+    key = rsa.generate_private_key(public_exponent=RSA_PUBLIC_EXPONENT, key_size=RSA_KEY_SIZE, backend=default_backend())
+    client_key = rsa.generate_private_key(public_exponent=RSA_PUBLIC_EXPONENT, key_size=RSA_KEY_SIZE, backend=default_backend())

68-70: LGTM - Server credentials properly configured for mTLS

The server credentials are correctly configured to require client authentication, implementing mutual TLS as described in the docstring.


72-83: Properly handles multiple alternative endpoints

The code correctly iterates through all provided endpoints, adds each to the server with secure credentials, and builds appropriate protobuf messages containing all necessary information for clients to establish secure connections.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/jumpstarter/jumpstarter/client/grpc.py (1)

272-278: Implement attribute error handling

The current implementation doesn't handle the case where the requested attribute doesn't exist on the stub objects. This could lead to confusing error messages since the AttributeError would come from the last stub, not necessarily from the one that would have been used.

    def __getattr__(self, name):
+       # First check if the attribute exists on any stub
+       attr_exists = False
+       for stub in self.__stubs.values():
+           if hasattr(stub, name):
+               attr_exists = True
+               break
+               
+       if not attr_exists:
+           raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
+
        for channel, stub in self.__stubs.items():
            # find the first channel that's ready
            if channel.get_state(try_to_connect=True) == ChannelConnectivity.READY:
                return getattr(stub, name)
        
        # or fallback to the last channel (via router)
        return getattr(next(reversed(self.__stubs.values())), name)
packages/jumpstarter/jumpstarter/common/utils.py (1)

100-107: Update function docstring to include the new parameter

The function docstring should be updated to document the use_alternative_endpoints parameter, explaining its purpose and effect when enabled.

    """Launch a shell with a custom prompt indicating the exporter type.

    Args:
        host: The jumpstarter host path
        context: The context of the shell ("local" or "remote")
        allow: List of allowed drivers
        unsafe: Whether to allow drivers outside of the allow list
+       use_alternative_endpoints: Whether to use alternative endpoints for communication
    """
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 872af90 and b8dd758.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • packages/jumpstarter-cli/jumpstarter_cli/shell.py (1 hunks)
  • packages/jumpstarter/jumpstarter/client/client.py (3 hunks)
  • packages/jumpstarter/jumpstarter/client/core.py (7 hunks)
  • packages/jumpstarter/jumpstarter/client/grpc.py (2 hunks)
  • packages/jumpstarter/jumpstarter/client/lease.py (2 hunks)
  • packages/jumpstarter/jumpstarter/common/utils.py (4 hunks)
  • packages/jumpstarter/jumpstarter/common/utils_test.py (1 hunks)
  • packages/jumpstarter/jumpstarter/config/client.py (3 hunks)
  • packages/jumpstarter/jumpstarter/config/client_config_test.py (3 hunks)
  • packages/jumpstarter/jumpstarter/config/env.py (1 hunks)
  • packages/jumpstarter/jumpstarter/config/exporter.py (3 hunks)
  • packages/jumpstarter/jumpstarter/exporter/exporter.py (2 hunks)
  • packages/jumpstarter/jumpstarter/exporter/session.py (4 hunks)
  • packages/jumpstarter/jumpstarter/exporter/tls.py (1 hunks)
  • packages/jumpstarter/pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • packages/jumpstarter/jumpstarter/config/env.py
  • packages/jumpstarter/jumpstarter/exporter/exporter.py
  • packages/jumpstarter/jumpstarter/config/exporter.py
  • packages/jumpstarter/pyproject.toml
  • packages/jumpstarter/jumpstarter/config/client_config_test.py
  • packages/jumpstarter/jumpstarter/client/core.py
  • packages/jumpstarter/jumpstarter/exporter/tls.py
  • packages/jumpstarter/jumpstarter/client/lease.py
  • packages/jumpstarter/jumpstarter/config/client.py
  • packages/jumpstarter/jumpstarter/client/client.py
  • packages/jumpstarter/jumpstarter/common/utils_test.py
  • packages/jumpstarter/jumpstarter/exporter/session.py
🧰 Additional context used
🧬 Code Definitions (1)
packages/jumpstarter/jumpstarter/client/grpc.py (2)
packages/jumpstarter/jumpstarter/config/client.py (1)
  • channel (57-63)
packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py (1)
  • ExporterServiceStub (489-524)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: pytest-matrix (3.11)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-devspace .devfile/Containerfile.client)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-dev .devfile/Containerfile)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter-utils Dockerfile.utils)
  • GitHub Check: build-and-push-image (jumpstarter-dev/jumpstarter Dockerfile)
🔇 Additional comments (8)
packages/jumpstarter/jumpstarter/client/grpc.py (3)

266-270: Update stub initialization to use standard constructor calls

The stub initialization approach here directly calls __init__ methods on a SimpleNamespace object, which is an unconventional pattern that might cause issues if the stub implementations change.

-        for channel in channels:
-            stub = SimpleNamespace()
-            jumpstarter_pb2_grpc.ExporterServiceStub.__init__(stub, channel)
-            router_pb2_grpc.RouterServiceStub.__init__(stub, channel)
-            self.__stubs[channel] = stub
+        for channel in channels:
+            stub = SimpleNamespace(
+                exporter=jumpstarter_pb2_grpc.ExporterServiceStub(channel),
+                router=router_pb2_grpc.RouterServiceStub(channel)
+            )
+            self.__stubs[channel] = stub

272-278: Handle case when no channels are ready

The __getattr__ method falls back to the last channel when no channels are ready, but it doesn't handle potential errors or document this behavior.

    def __getattr__(self, name):
        for channel, stub in self.__stubs.items():
            # find the first channel that's ready
            if channel.get_state(try_to_connect=True) == ChannelConnectivity.READY:
                return getattr(stub, name)
+       
+       if not self.__stubs:
+           raise RuntimeError("No channels available to create stub")
+           
        # or fallback to the last channel (via router)
+       import logging
+       logger = logging.getLogger(__name__)
+       logger.debug("No ready channels found, falling back to last channel")
        return getattr(next(reversed(self.__stubs.values())), name)

259-263: LGTM: Well-designed class structure

The SmartExporterStub is well-designed with a frozen dataclass that manages multiple channels and their corresponding stubs. The use of OrderedDict ensures consistent fallback behavior when no ready channels are found.

packages/jumpstarter-cli/jumpstarter_cli/shell.py (1)

34-40: Parameter added correctly to support alternative endpoints

The new parameter use_alternative_endpoints is properly passed from the config object to the launch_shell function, following the established pattern for other configuration parameters. This change integrates well with the overall PR objective of adding support for alternative endpoints.

packages/jumpstarter/jumpstarter/common/utils.py (4)

10-10: Import added correctly for new environment variable

The import statement correctly includes the new JMP_USE_ALT_ENDPOINTS environment variable from the appropriate module.


55-64: Environment variable handling implemented properly in env_async

The changes properly retrieve and parse the JMP_USE_ALT_ENDPOINTS environment variable, converting it to a boolean value. The implementation follows the pattern established for other environment variables in the codebase, providing a sensible default of "0" (false) if the variable is not set.

The parameter is correctly passed to the client_from_path function, maintaining a consistent approach to configuration handling.


92-99: Function signature updated with backward compatibility

The launch_shell function signature has been properly updated to include use_alternative_endpoints as a keyword-only parameter with a default value of False. This approach maintains backward compatibility while adding the new functionality, addressing the feedback from previous reviews.

Using a keyword-only parameter (with *,) is an appropriate design choice as it enforces explicit naming when this parameter is used, which improves code readability.


122-122: Environment variable set correctly in launch_shell

The JMP_USE_ALT_ENDPOINTS environment variable is properly set based on the function parameter value, following the established pattern for other environment variables in this function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: No status

Development

Successfully merging this pull request may close these issues.

Fully authenticated, tls/token/lease based direct connectivity client->exporter bypassing router, where available.

4 participants