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

FUZ-22 - API Token improvements - Tool Segmentation #1061

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

gitstart
Copy link
Contributor

@gitstart gitstart commented Mar 10, 2025

This PR was created by GitStart to address the requirements from this ticket: FUZ-22.


This PR adds tool-specific segmentation to users to enhance security in FuzzManager. Currently, API tokens (and thus users given the 1:1 mapping) have unrestricted access across all tools, creating potential security risks if compromised.

The changes:

  • Restrict users to specific tools

  • Add Django commands add_tool_to_user and remove_tool_from_user for user-tool management

  • Implement user restrictions based on tool access

  • Prevent unauthorized tool access

This segmentation limits the impact of potential token leaks and provides better access control for crash/coverage reporting.

Demo

https://www.loom.com/share/c26267c717214c44be30ebe21ea7e60c

Test Plan

1. Setup Test Environment

Run the following in python manage.py shell to prepare for testing:

from django.contrib.auth.models import User, Permission
from crashmanager.models import Tool, User as CrashManagerUser, CrashEntry, CrashEntry_save
from covmanager.models import Repository
from rest_framework.authtoken.models import Token
from django.db.models.signals import post_save

# Disconnect Celery signals to avoid Redis dependency
post_save.disconnect(receiver=CrashEntry_save, sender=CrashEntry)

# Create test repository
testrepo, _ = Repository.objects.get_or_create(
    name="testrepo", 
    defaults={"location": "https://github.com/example/testrepo.git"}
)

# Get required permissions
view_perm = Permission.objects.get(codename="view_crashmanager")
report_perm = Permission.objects.get(codename="crashmanager_report_crashes")

# Create test tools
tool1, _ = Tool.objects.get_or_create(name="tool1")
tool2, _ = Tool.objects.get_or_create(name="tool2")

# Setup restricted user with only tool1 access
restricted_user, _ = User.objects.get_or_create(username="restricted_tester")
restricted_token, _ = Token.objects.get_or_create(user=restricted_user)
restricted_cm_user, _ = CrashManagerUser.objects.get_or_create(user=restricted_user)
restricted_cm_user.restricted = True
restricted_cm_user.save()
restricted_cm_user.defaultToolsFilter.clear()
restricted_cm_user.defaultToolsFilter.add(tool1)
restricted_user.user_permissions.add(view_perm, report_perm)
restricted_user.save()

# Setup unrestricted user
unrestricted_user, _ = User.objects.get_or_create(username="unrestricted_tester")
unrestricted_token, _ = Token.objects.get_or_create(user=unrestricted_user)
unrestricted_cm_user, _ = CrashManagerUser.objects.get_or_create(user=unrestricted_user)
unrestricted_cm_user.restricted = False
unrestricted_cm_user.save()
unrestricted_user.user_permissions.add(view_perm, report_perm)
unrestricted_user.save()

print(f"Restricted user token: {restricted_token.key}")
print(f"Unrestricted user token: {unrestricted_token.key}")

2. Test Scenarios

A. Restricted User Tests

  1. Successfully create a crash with allowed tool:

       curl -X POST http://localhost:8000/crashmanager/rest/crashes/ \
         -H "Authorization: Token RESTRICTED_TOKEN" \
         -H "Content-Type: application/json" \
         -d '{
           "tool": "tool1",
           "platform": "x86_64",
           "product": "mozilla-central",
           "os": "linux",
           "client": "testclient",
           "rawStdout": "test stdout",
           "rawStderr": "test stderr",
           "rawCrashData": "test crash",
           "testcase_content": "test case",
           "testcase_ext": "txt",
           "testcase_size": 9,
           "testcase_quality": 0,
           "metadata": "", "env": "", "args": ""
         }'
    
  2. Attempt to create a crash with unauthorized tool (should fail):

   curl -X POST http://localhost:8000/crashmanager/rest/crashes/ \
     -H "Authorization: Token RESTRICTED_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "tool": "tool2",
       "platform": "x86_64",
       "product": "mozilla-central",
       "os": "linux",
       "client": "testclient",
       "rawStdout": "test stdout",
       "rawStderr": "test stderr",
       "rawCrashData": "test crash",
       "testcase_content": "test case",
       "testcase_ext": "txt",
       "testcase_size": 9,
       "testcase_quality": 0,
       "metadata": "", "env": "", "args": ""
     }'
  1. Attempt to create a collection with mixed tools (should fail):
   curl -X POST http://localhost:8000/covmanager/rest/collections/ \
     -H "Authorization: Token RESTRICTED_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "repository": "testrepo",
       "description": "test",
       "coverage": "{\"linesTotal\": 0, \"name\": null, \"coveragePercent\": 0.0, \"children\": {}, \"linesMissed\": 0, \"linesCovered\": 0}",
       "branch": "master",
       "revision": "abc",
       "client": "testclient",
       "tools": "tool1,tool2"
     }'

B. Unrestricted User Tests

Create a collection with multiple tools (should succeed):

   curl -X POST http://localhost:8000/covmanager/rest/collections/ \
     -H "Authorization: Token UNRESTRICTED_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "repository": "testrepo",
       "description": "test",
       "coverage": "{\"linesTotal\": 0, \"name\": null, \"coveragePercent\": 0.0, \"children\": {}, \"linesMissed\": 0, \"linesCovered\": 0}",
       "branch": "master",
       "revision": "abc",
       "client": "testclient",
       "tools": "tool1,tool2"
     }'

3. Tool Assignment Management

Use these management commands to add or remove tools from users:

  • Add tool to user:

    python manage.py add_tool_to_user <username> <tool_name>
    
  • Remove tool from user:

    python manage.py remove_tool_from_user <username> <tool_name>
    

Additional Notes:

The test suite has been updated to correctly reflect the new security model:

  • Test fixtures now properly assign tools to restricted users before testing

  • Tests verify that restricted users can only report crashes for tools they have permission to use

  • Unauthorized tool access is properly prevented and tested

Copy link

No Taskcluster jobs started for this pull request

The allowPullRequests configuration for this repository (in .taskcluster.yml on the default branch) does not allow starting tasks for this pull request.

@gitstart gitstart marked this pull request as ready for review March 13, 2025 16:57
@gitstart gitstart requested a review from a team as a code owner March 13, 2025 16:57
Comment on lines 720 to 746
def create(self, request, *args, **kwargs):
"""Check user has access to tools before creation"""
tools_str = request.data.get("tools")
if tools_str is None:
return Response(
{"message": "Missing required 'tools' parameter"},
status=status.HTTP_400_BAD_REQUEST,
)

requested_tools = set(tools_str.split(","))

user = User.get_or_create_restricted(request.user)[0]
if user.restricted:
allowed_tools = set(user.defaultToolsFilter.values_list("name", flat=True))
if not allowed_tools:
raise PermissionDenied({"message": "No tools assigned to user"})

unauthorized_tools = requested_tools - allowed_tools
if unauthorized_tools:
raise PermissionDenied(
{
"message": f"You don't have permission to use the following tools: {', '.join(unauthorized_tools)}"
}
)

return super().create(request, *args, **kwargs)

Copy link
Contributor

Choose a reason for hiding this comment

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

Hi @jschwartzentruber,

This update looks very similar to the one in crashmanager/view.py. We also noticed a few other functions in both views that share similarities. However, to avoid circular imports, we've kept them separate.

Would you prefer abstracting these similar logic pieces elsewhere in the codebase to reduce duplication?

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