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

Add secret-key guard for team endpoints #1251

Merged
merged 1 commit into from
Nov 5, 2024
Merged

Add secret-key guard for team endpoints #1251

merged 1 commit into from
Nov 5, 2024

Conversation

mdmohsin7
Copy link
Collaborator

@mdmohsin7 mdmohsin7 commented Nov 5, 2024

/v1/plugins/{plugin_id}/approve, /v1/plugins/{plugin_id}/reject, /v1/plugins/public/unapproved will require admin key to be passed as a header with key secret-key

Summary by CodeRabbit

  • New Features
    • Enhanced security for plugin management by requiring a secret_key for authorization on specific endpoints.
  • Bug Fixes
    • Implemented checks to prevent unauthorized access to plugin approval and rejection functionalities.

Copy link
Contributor

coderabbitai bot commented Nov 5, 2024

Walkthrough

The changes to the backend/routers/plugins.py file enhance the security of specific endpoints by requiring a secret_key header for authorization. The functions approve_plugin, reject_plugin, and get_unapproved_public_plugins now validate this key against an environment variable called ADMIN_KEY, returning an HTTP 403 error if the key does not match. Additionally, the import statement for Header from fastapi.params has been added to support these checks, while minor formatting adjustments were also made.

Changes

File Change Summary
backend/routers/plugins.py - Added secret_key parameter to approve_plugin, reject_plugin, and get_unapproved_public_plugins functions.
- Implemented authorization check against ADMIN_KEY.
- Added import statement for Header from fastapi.params.
- Minor formatting adjustments.

Poem

🐇 In the code where plugins play,
A secret key now leads the way.
With checks in place, the bunnies cheer,
For safety's song is crystal clear!
Hops of joy, let’s celebrate,
Secure and swift, we navigate! 🌟


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 (2)
backend/routers/plugins.py (2)

175-175: Fix parameter formatting

Add a space after the comma in the parameter list for better readability.

-def reject_plugin(plugin_id: str,secret_key: str = Header(...)):
+def reject_plugin(plugin_id: str, secret_key: str = Header(...)):

Line range hint 167-195: Consider additional security measures for admin endpoints

The current implementation could benefit from these architectural improvements:

  1. Environment variable validation:

    • Validate ADMIN_KEY presence at application startup
    • Ensure minimum length/complexity requirements
  2. Audit logging:

    • Log all admin actions (approvals/rejections)
    • Include timestamps and admin identifiers
    • Consider using structured logging
  3. Authentication strategy:

    • Consider implementing token-based auth instead of a single shared key
    • Add key rotation capability
    • Consider implementing role-based access control (RBAC)

Would you like help implementing any of these suggestions?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 795748d and 0f12109.

📒 Files selected for processing (1)
  • backend/routers/plugins.py (3 hunks)
🔇 Additional comments (1)
backend/routers/plugins.py (1)

9-9: LGTM: Header import added correctly

The import is properly placed with other FastAPI imports and is necessary for the new secret key validation.

Comment on lines +175 to +177
def reject_plugin(plugin_id: str,secret_key: str = Header(...)):
if secret_key != os.getenv('ADMIN_KEY'):
raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apply consistent security improvements

Apply the same security enhancements as suggested for the approve_plugin endpoint:

  1. Use constant-time comparison
  2. Add rate limiting
  3. Add return type hint
 @router.post('/v1/plugins/{plugin_id}/reject', tags=['v1'])
-def reject_plugin(plugin_id: str, secret_key: str = Header(...)):
+def reject_plugin(plugin_id: str, secret_key: str = Header(...)) -> dict[str, str]:
+    from hmac import compare_digest
-    if secret_key != os.getenv('ADMIN_KEY'):
+    if not compare_digest(secret_key, os.getenv('ADMIN_KEY', '')):
         raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
     change_plugin_approval_status(plugin_id, False)
     return {'status': 'ok'}
📝 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
def reject_plugin(plugin_id: str,secret_key: str = Header(...)):
if secret_key != os.getenv('ADMIN_KEY'):
raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
def reject_plugin(plugin_id: str, secret_key: str = Header(...)) -> dict[str, str]:
from hmac import compare_digest
if not compare_digest(secret_key, os.getenv('ADMIN_KEY', '')):
raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
change_plugin_approval_status(plugin_id, False)
return {'status': 'ok'}

Comment on lines +193 to +195
def get_unapproved_public_plugins(secret_key: str = Header(...)):
if secret_key != os.getenv('ADMIN_KEY'):
raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

Security and performance improvements needed

The verification confirms that:

  1. The database layer fetches all unapproved plugins at once without pagination (get_unapproved_public_plugins_db() returns full list)
  2. No existing pagination patterns were found in the codebase

The original review comment is valid as:

  • Fetching all unapproved plugins at once could lead to performance issues and potential DoS
  • The security concerns about constant-time comparison are legitimate for admin key validation
  • Return type hints would improve code maintainability

The suggested changes in the review should be implemented:

  • Add pagination parameters and slice the results
  • Use compare_digest for secure key comparison
  • Add return type hints for better type safety
🔗 Analysis chain

Enhance security and add pagination

While the basic security is implemented, consider these improvements:

  1. Security enhancements:
    • Use constant-time comparison
    • Add rate limiting
    • Add return type hint
  2. Add pagination to prevent potential DoS from large datasets
 @router.get('/v1/plugins/public/unapproved', tags=['v1'])
-def get_unapproved_public_plugins(secret_key: str = Header(...)):
+def get_unapproved_public_plugins(
+    secret_key: str = Header(...),
+    skip: int = 0,
+    limit: int = 50
+) -> list[Plugin]:
+    from hmac import compare_digest
-    if secret_key != os.getenv('ADMIN_KEY'):
+    if not compare_digest(secret_key, os.getenv('ADMIN_KEY', '')):
         raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
     plugins = get_unapproved_public_plugins_db()
-    return plugins
+    return plugins[skip:skip + limit]

Let's verify the potential impact of large datasets:


The results show that the database layer fetches all unapproved plugins at once without pagination, which confirms the review's concern. Let me verify if there are any existing pagination patterns in the codebase that we should follow.


Let me check for any pagination-related query parameters in the FastAPI routes to understand the existing patterns.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there's any pagination implementation in the database layer
rg -A 5 "get_unapproved_public_plugins_db"

Length of output: 1356


Script:

#!/bin/bash
# Check for existing pagination patterns in the codebase
rg -B 2 -A 2 "skip.*limit" backend/

Length of output: 37


Script:

#!/bin/bash
# Look for FastAPI route definitions with query parameters
ast-grep --pattern 'def $_($$$, skip: int = $_,$$$):'

Length of output: 55

Comment on lines +167 to +169
def approve_plugin(plugin_id: str, secret_key: str = Header(...)):
if secret_key != os.getenv('ADMIN_KEY'):
raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

Based on the search results, I can now generate the final response since I have enough context about the security implementation in the codebase. There's no existing rate limiting mechanism, and the security suggestions in the review are valid.

Security improvements needed for admin endpoint

The suggested security enhancements are valid and necessary:

  1. Using compare_digest is essential to prevent timing attacks on the admin key comparison
  2. Rate limiting is missing and should be implemented to prevent brute-force attempts
  3. Return type hint addition improves type safety

The suggested code changes are appropriate:

from hmac import compare_digest

def approve_plugin(plugin_id: str, secret_key: str = Header(...)) -> dict[str, str]:
    if not compare_digest(secret_key, os.getenv('ADMIN_KEY', '')):
        raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
🔗 Analysis chain

Enhance security implementation

While the basic secret key validation is implemented correctly, consider these security improvements:

  1. Use a secure constant-time comparison to prevent timing attacks
  2. Add rate limiting to prevent brute-force attempts
  3. Add return type hint for better type safety

Apply this enhancement:

 @router.post('/v1/plugins/{plugin_id}/approve', tags=['v1'])
-def approve_plugin(plugin_id: str, secret_key: str = Header(...)):
+def approve_plugin(plugin_id: str, secret_key: str = Header(...)) -> dict[str, str]:
+    from hmac import compare_digest
     if secret_key != os.getenv('ADMIN_KEY'):
-        raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
+    if not compare_digest(secret_key, os.getenv('ADMIN_KEY', '')):
+        raise HTTPException(status_code=403, detail='You are not authorized to perform this action')
     change_plugin_approval_status(plugin_id, True)
     return {'status': 'ok'}

Let's verify if rate limiting is implemented elsewhere:


Let me search for any existing security-related code or middleware that might handle rate limiting.


Let me check for any existing security utilities or authentication middleware that might be relevant.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for rate limiting middleware or decorators
rg -l "RateLimiter|rate_limit" --type py

Length of output: 1356


Script:

#!/bin/bash
# Check for security middleware and rate limiting
rg -l "middleware|limiter|security" --type py

# Check FastAPI dependencies and security utilities
rg -A 5 "from fastapi import.*security" --type py
rg -A 5 "from fastapi import.*Depends" --type py

Length of output: 4882


Script:

#!/bin/bash
# Check for auth utilities and security implementations
rg -A 5 "utils.other import endpoints as auth" --type py

# Check the auth endpoints implementation
rg -A 10 "class endpoints" backend/utils/other.py

Length of output: 4227

@beastoin beastoin merged commit d192103 into main Nov 5, 2024
1 check passed
@beastoin beastoin deleted the unapproved-guard branch November 5, 2024 12:44
@beastoin
Copy link
Collaborator

beastoin commented Nov 5, 2024

lgtm @mdmohsin7 🥳

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

Successfully merging this pull request may close these issues.

2 participants