Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix(dashmate): port marks as closed if ipv6 is not disabled #2162

Merged
merged 5 commits into from
Sep 26, 2024

Conversation

shumkov
Copy link
Member

@shumkov shumkov commented Sep 22, 2024

Issue being fixed or feature implemented

Issue #2100

What was done?

  • Forced HTTP client to use IPv4

How Has This Been Tested?

Running locally

Breaking Changes

None

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • Enhanced port status checking with improved HTTP request handling using the https module.
    • Integrated error handling and response size management for better robustness.
  • Bug Fixes

    • Removed outdated requestText method to streamline functionality.

@QuantumExplorer
Copy link
Member

@shumkov can you address the security issues found?

Copy link
Contributor

coderabbitai bot commented Sep 26, 2024

Walkthrough

The changes involve replacing the requestText function with a new implementation for making HTTPS requests directly within the checkPortStatus method of the mnowatch object. The updated method manages the entire request lifecycle, including timeouts and response handling, while enforcing a maximum response size of 1 MB. Error handling has been enhanced to log warnings in case of request failures or timeouts, improving the overall robustness of the port status checking functionality.

Changes

Files Change Summary
packages/dashmate/src/status/providers.js Removed requestText(url) method; modified checkPortStatus(port) to handle HTTPS requests directly.

Poem

In the meadow where bunnies play,
A new way to check ports came our way.
With HTTPS now, so swift and bright,
We hop with joy, our future's in sight!
No more old ways, we leap and bound,
Robust and ready, new magic we've found! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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 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.

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: 3

🧹 Outside diff range and nitpick comments (1)
packages/dashmate/src/status/providers.js (1)

98-112: Potential for multiple promise resolutions

There are multiple paths that can resolve the promise (e.g., data size exceeding the maximum, timeouts, errors). Since a JavaScript promise ignores subsequent calls to resolve or reject after the first one, ensure that no code relies on multiple resolutions, and consider adding safeguards if necessary.

While this is generally acceptable in JavaScript, adding comments to clarify that only the first resolution takes effect can improve code readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7f79545 and 3ddb35f.

📒 Files selected for processing (1)
  • packages/dashmate/src/status/providers.js (2 hunks)
🔇 Additional comments (3)
packages/dashmate/src/status/providers.js (3)

67-71: Well-documented reason for using IPv4

The comments clearly explain the rationale behind switching to https.request and forcing IPv4 usage to avoid connection issues with IPv6 addresses. This improves code maintainability by providing context for future developers.


77-77: Correctly forcing IPv4 with the 'family' option

Setting family: 4 in the request options correctly ensures that the DNS lookup will return IPv4 addresses, which aligns with the need specified in the comments.


72-78: Security consideration: Ensure TLS certificate validation is active

When using https.request, TLS certificate validation is enabled by default. It's important to confirm that no options inadvertently disable this feature, which could expose the application to man-in-the-middle attacks.

Run the following script to verify that certificate validation is not disabled elsewhere in the codebase:

Comment on lines 87 to 96
res.setTimeout(MAX_REQUEST_TIMEOUT, () => {
if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.warn(`Port check ${MAX_REQUEST_TIMEOUT} timeout reached`);
}

resolve(PortStateEnum.ERROR);

req.destroy();
});
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 26, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Redundant timeout settings on both request and response

Timeouts are set on both the response (res.setTimeout) and the request (req.setTimeout) with the same MAX_REQUEST_TIMEOUT value. This redundancy may not be necessary and could lead to unexpected behavior or complexity.

Consider removing one of the timeout settings to simplify the code. Typically, setting the timeout on the request (req.setTimeout) is sufficient to handle slow responses and overall request duration.

Also applies to: 129-138

Copy link
Member Author

Choose a reason for hiding this comment

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

Request and Response timeouts aren't the same thing. Setting both timeouts is correct approach. In this case better to use options.timeout that set socket timeout for both request and response.

Copy link
Contributor

Choose a reason for hiding this comment

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

@shumkov, thank you for the clarification. You're correct that request and response timeouts aren't the same, and setting both is the correct approach. Using options.timeout to set the socket timeout for both is a better solution.

(\ (
( ••)
o
(")(")


✏️ Learnings added
Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:87-96
Timestamp: 2024-09-26T14:58:26.258Z
Learning: In the `checkPortStatus` function, it's better to use `options.timeout` to set the socket timeout for both request and response.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:121-127
Timestamp: 2024-09-26T14:28:54.892Z
Learning: In the `mnowatch.checkPortStatus` function, failures should resolve with `PortStateEnum.ERROR` instead of rejecting the promise.

Comment on lines 121 to 127
if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.warn(e);
}

resolve(PortStateEnum.ERROR);
});
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 26, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Error handling may obscure specific errors

In the req.on('error', (e) => { ... }) handler, the promise resolves with PortStateEnum.ERROR, which could obscure the specific error encountered during the request.

Consider rejecting the promise with the error to allow for more granular error handling upstream or include the error details in the resolution. Here's an example:

req.on('error', (e) => {
  if (process.env.DEBUG) {
    // eslint-disable-next-line no-console
    console.warn(e);
  }
- resolve(PortStateEnum.ERROR);
+ reject(e);
});

Alternatively, if resolving with PortStateEnum.ERROR is necessary, ensure that the error details are logged or otherwise accessible for debugging purposes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.warn(e);
}
resolve(PortStateEnum.ERROR);
});
if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.warn(e);
}
reject(e);
});

Copy link
Member Author

Choose a reason for hiding this comment

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

In case of failure we return error status.

Copy link
Contributor

Choose a reason for hiding this comment

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

@shumkov, understood. Thank you for the clarification.

(_/)
( ..)
(")
(")


✏️ Learnings added
Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:121-127
Timestamp: 2024-09-26T14:28:54.731Z
Learning: In the `mnowatch.checkPortStatus` function, failures should resolve with `PortStateEnum.ERROR` instead of rejecting the promise.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +115 to +117
res.on('end', () => {
resolve(data);
});
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 26, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing HTTP status code verification

Upon receiving the response, the code does not check the HTTP status code before resolving with the data. This could lead to handling error responses (e.g., 4xx or 5xx status codes) as if they were successful, which may cause issues downstream.

Consider verifying the res.statusCode and handle non-200 status codes appropriately. Here's a suggested change:

// Handle the end of the response
res.on('end', () => {
+   if (res.statusCode !== 200) {
+     if (process.env.DEBUG) {
+       // eslint-disable-next-line no-console
+       console.warn(`Received non-200 status code: ${res.statusCode}`);
+     }
+     resolve(PortStateEnum.ERROR);
+     return;
+   }
    resolve(data);
});

Committable suggestion was skipped due to low confidence.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point! But it's better to do when we just got a response.

Copy link
Contributor

Choose a reason for hiding this comment

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

@shumkov, you're absolutely correct. Checking the status code immediately when we receive the response is a better approach. Here's how you might adjust the code:

return new Promise((resolve) => {
  const req = https.request(options, (res) => {
    let data = '';

+   // Check if the status code is 200
+   if (res.statusCode !== 200) {
+     if (process.env.DEBUG) {
+       // eslint-disable-next-line no-console
+       console.warn(`Port check request failed with status code ${res.statusCode}`);
+     }
+     // Consume response data to free up memory
+     res.resume();
+     resolve(PortStateEnum.ERROR);
+     return;
+   }

    // Optionally set the encoding to receive strings directly
    res.setEncoding('utf8');

    // Collect data chunks
    res.on('data', (chunk) => {
      data += chunk;

      if (data.length > MAX_RESPONSE_SIZE) {
        resolve(PortStateEnum.ERROR);

        if (process.env.DEBUG) {
          // eslint-disable-next-line no-console
          console.warn('Port check response size exceeded');
        }

        req.destroy();
      }
    });

    // Handle the end of the response
    res.on('end', () => {
      resolve(data);
    });
  });

  // Handle request errors
  req.on('error', (e) => {
    if (process.env.DEBUG) {
      // eslint-disable-next-line no-console
      console.warn(`Port check request failed: ${e}`);
    }

    resolve(PortStateEnum.ERROR);
  });

  req.end();
});

I've noted this for future reviews.

(•ᴥ•)ゝ


✏️ Learnings added
Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:117-119
Timestamp: 2024-09-26T16:21:07.033Z
Learning: In the `checkPortStatus` function, it's better to check the HTTP status code immediately upon receiving the response in the `https.request` callback, rather than in the `res.on('end')` callback.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:121-127
Timestamp: 2024-09-26T14:28:54.892Z
Learning: In the `mnowatch.checkPortStatus` function, failures should resolve with `PortStateEnum.ERROR` instead of rejecting the promise.

Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:87-96
Timestamp: 2024-09-26T14:58:26.560Z
Learning: In the `checkPortStatus` function, it's better to use `options.timeout` to set the socket timeout for both request and response.

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

🧹 Outside diff range and nitpick comments (1)
packages/dashmate/src/status/providers.js (1)

67-82: LGTM: Improved implementation addressing IPv6 issue

The changes effectively address the IPv6 problem mentioned in the PR objectives by forcing IPv4 usage. The implementation using the HTTPS module with a Promise-based approach is well-structured.

Consider adding a catch block to the Promise to handle any unexpected errors that might occur during the request setup:

return new Promise((resolve, reject) => {
  try {
    const req = https.request(options, (res) => {
      // ... existing code ...
    });
    // ... existing code ...
  } catch (error) {
    if (process.env.DEBUG) {
      console.warn(`Error setting up request: ${error}`);
    }
    resolve(PortStateEnum.ERROR);
  }
});

This addition would ensure that any errors during the request setup are properly handled and don't cause unhandled promise rejections.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3ddb35f and 3f6ee40.

📒 Files selected for processing (1)
  • packages/dashmate/src/status/providers.js (2 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/dashmate/src/status/providers.js (2)
Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:87-96
Timestamp: 2024-09-26T14:58:26.560Z
Learning: In the `checkPortStatus` function, it's better to use `options.timeout` to set the socket timeout for both request and response.
Learnt from: shumkov
PR: dashpay/platform#2162
File: packages/dashmate/src/status/providers.js:121-127
Timestamp: 2024-09-26T14:28:54.892Z
Learning: In the `mnowatch.checkPortStatus` function, failures should resolve with `PortStateEnum.ERROR` instead of rejecting the promise.
🔇 Additional comments (5)
packages/dashmate/src/status/providers.js (5)

1-5: LGTM: Imports and constants are well-defined

The addition of the 'https' import and the definition of MAX_RESPONSE_SIZE are good practices. The MAX_RESPONSE_SIZE constant helps prevent potential DoS attacks by limiting the response size.


83-119: LGTM: Robust response handling implementation

The response handling is well-implemented with several good practices:

  1. Status code check to ensure only successful responses are processed.
  2. Efficient data collection in chunks.
  3. Maximum response size check to prevent potential memory issues.
  4. Consistent error handling and debugging logs.

These changes significantly improve the robustness of the checkPortStatus function.


122-129: LGTM: Consistent error handling

The error handling implementation is consistent with the project's conventions:

  1. Errors are logged only when the DEBUG environment variable is set, which is good for production environments.
  2. The function resolves with PortStateEnum.ERROR on error, aligning with the learned practice from previous discussions.

This approach ensures consistent error handling across the codebase.


131-132: LGTM: Proper request finalization

The req.end() call is correctly placed to finalize and send the request. This is a crucial step in the HTTPS request process.


67-132: Summary: Effective solution with robust implementation

The changes in this PR effectively address the IPv6 issue mentioned in the objectives while maintaining high code quality. Key points:

  1. Forced IPv4 usage resolves the original problem.
  2. Robust error handling and response processing improve reliability.
  3. Consistent coding practices and well-placed comments enhance maintainability.

Regarding the security concern raised by QuantumExplorer:
The implementation includes several security-conscious practices such as:

  • Limiting the response size to prevent potential DoS attacks.
  • Using HTTPS for secure communication.
  • Proper error handling to prevent information leakage.

No apparent security vulnerabilities have been introduced by these changes. However, it's always beneficial to have a dedicated security review to ensure all aspects have been considered.

@QuantumExplorer QuantumExplorer merged commit 2ea168a into v1.4-dev Sep 26, 2024
23 checks passed
@QuantumExplorer QuantumExplorer deleted the feat/dashmate/port-check-ipv4 branch September 26, 2024 17:05
@coderabbitai coderabbitai bot mentioned this pull request Sep 28, 2024
6 tasks
@thephez
Copy link
Collaborator

thephez commented Sep 30, 2024

@shumkov can I remove the suggestion to disable IPv6 now? It was added in dashpay/docs#390

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

bug(dashmate): Port check may return closed in case VPS is configured for IPv6
3 participants