Skip to content

FUZ-22 - API Token improvements - IP Restriction #26

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

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 0 additions & 108 deletions .taskcluster.yml
Original file line number Diff line number Diff line change
@@ -28,22 +28,11 @@ tasks:
then: ${event.pull_request.base.repo.clone_url}
else: ${event.repository.clone_url}

codecov_secret:
codecov-fuzzmanager

pypi_secret:
pypi-fuzzmanager

project_name:
FuzzManager

matrix:
language: python
secrets:
- type: env
secret: project/fuzzing/codecov-fuzzmanager
name: CODECOV_TOKEN
key: token
script:
- bash
- '-xec'
@@ -100,24 +89,6 @@ tasks:
npm run test;
npm run production;
npm run codecov;
- name: PyPI upload
version: "3.9"
env:
TOXENV: pypi
script:
- tox
when:
release: true
all_passed: true
secrets:
- type: env
secret: project/fuzzing/pypi-fuzzmanager
name: TWINE_USERNAME
key: username
- type: env
secret: project/fuzzing/pypi-fuzzmanager
name: TWINE_PASSWORD
key: password

in:
$if: >
@@ -152,88 +123,9 @@ tasks:
- queue:create-task:highest:proj-fuzzing/ci
- queue:create-task:highest:proj-fuzzing/ci-*
- queue:scheduler-id:taskcluster-github
- secrets:get:project/fuzzing/${codecov_secret}
- secrets:get:project/fuzzing/${pypi_secret}
metadata:
name: ${project_name} CI decision
description: Schedule CI tasks for ${project_name}
owner: '${user}@users.noreply.github.com'
source: ${http_repo}/raw/${fetch_rev}/.taskcluster.yml
- taskId: {$eval: as_slugid("docker")}
taskGroupId: ${task_group}
provisionerId: proj-fuzzing
workerType: ci
dependencies: []
created: {$fromNow: ''}
deadline: {$fromNow: '1 hour'}
payload:
image:
namespace: project.fuzzing.orion.orion-builder.master
path: public/orion-builder.tar.zst
type: indexed-image
maxRunTime: 3600
capabilities:
privileged: true
env:
LOAD_DEPS: "0"
GIT_REPOSITORY: ${http_repo}
GIT_REVISION: ${fetch_rev}
BUILD_TOOL: podman
DOCKERFILE: Dockerfile
IMAGE_NAME: mozillasecurity/fuzzmanager
ARCHIVE_PATH: /image.tar
command:
- sh
- -c
- uname -a && exec build
artifacts:
public/fuzzmanager.tar.zst:
expires: {$fromNow: '6 months'}
path: /image.tar.zst
type: file
scopes:
- docker-worker:capability:privileged
metadata:
name: FuzzManager Docker build
description: FuzzManager Docker build
owner: '${user}@users.noreply.github.com'
source: ${http_repo}/raw/${fetch_rev}/.taskcluster.yml
- $if: 'tasks_for in ["github-push"] && fetch_ref == "refs/heads/master"'
then:
taskId: {$eval: as_slugid("docker_push")}
taskGroupId: ${task_group}
provisionerId: proj-fuzzing
workerType: ci
dependencies:
- {$eval: as_slugid("docker")}
created: {$fromNow: ''}
deadline: {$fromNow: '1 hour'}
payload:
capabilities:
privileged: true
image:
namespace: project.fuzzing.orion.orion-builder.master
path: public/orion-builder.tar.zst
type: indexed-image
maxRunTime: 3600
features:
taskclusterProxy: true
env:
TASK_ID: {$eval: as_slugid("docker")}
TASKCLUSTER_SECRET: "project/fuzzing/docker-hub"
GIT_REPOSITORY: ${http_repo}
GIT_REVISION: ${fetch_rev}
SERVICE_NAME: fuzzmanager
command:
- sh
- -c
- uname -a && exec push
scopes:
- docker-worker:capability:privileged
- secrets:get:project/fuzzing/docker-hub
metadata:
name: FuzzManager Docker push
description: FuzzManager Docker push
owner: '${user}@users.noreply.github.com'
source: ${http_repo}/raw/${fetch_rev}/.taskcluster.yml
else: []
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ USER node
WORKDIR /src

RUN npm install
RUN npm run build
RUN npm run production

FROM python:3.10-alpine as backend

11 changes: 6 additions & 5 deletions server/covmanager/views.py
Original file line number Diff line number Diff line change
@@ -10,10 +10,11 @@
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.csrf import csrf_exempt
from rest_framework import filters, mixins, viewsets
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.authentication import SessionAuthentication

from crashmanager.models import Tool
from server.views import JsonQueryFilterBackend, SimpleQueryFilterBackend
from server.utils import IPRestrictedTokenAuthentication

from .models import Collection, Report, ReportConfiguration, ReportSummary, Repository
from .serializers import (
@@ -705,7 +706,7 @@ class CollectionViewSet(
API endpoint that allows adding/viewing Collections
"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = Collection.objects.all()
serializer_class = CollectionSerializer
paginate_by_param = "limit"
@@ -754,7 +755,7 @@ class ReportViewSet(
API endpoint that allows viewing Reports
"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = Report.objects.all()
serializer_class = ReportSerializer
paginate_by_param = "limit"
@@ -778,7 +779,7 @@ class RepositoryViewSet(
API endpoint that allows viewing Repositories
"""

authentication_classes = (TokenAuthentication,)
authentication_classes = (IPRestrictedTokenAuthentication,)
queryset = Repository.objects.all()
serializer_class = RepositorySerializer
filter_backends = [JsonQueryFilterBackend]
@@ -819,7 +820,7 @@ class ReportConfigurationViewSet(
API endpoint that allows adding/updating/viewing Report Configurations
"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = ReportConfiguration.objects.all()
serializer_class = ReportConfigurationSerializer
filter_backends = [
31 changes: 31 additions & 0 deletions server/crashmanager/tests/test_ip_restricted_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pytest
from django.conf import settings
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
import requests

@pytest.fixture
def allowed_ip():
return "127.0.0.1"

@pytest.fixture
def blocked_ip():
return "203.0.113.10"

@pytest.fixture(autouse=True)
def override_settings(allowed_ip):
settings.ALLOWED_IPS = [allowed_ip]

@pytest.mark.django_db
def test_allowed_ip_can_authenticate(api_client, user_normal, allowed_ip):
"""Ensure authentication works for an allowed IP"""
response = api_client.get("/crashmanager/rest/crashes/", REMOTE_ADDR=allowed_ip)
assert response.status_code == 200

@pytest.mark.django_db
def test_blocked_ip_is_rejected(api_client, allowed_ip, blocked_ip):
"""Ensure authentication fails for a blocked IP"""
response = api_client.get("/crashmanager/rest/crashes/", REMOTE_ADDR=blocked_ip)
assert response.status_code == 403
assert response.json()['detail'] == 'IP address restricted. Access denied.'

17 changes: 9 additions & 8 deletions server/crashmanager/views.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
from django.views.generic.list import ListView
from notifications.models import Notification
from rest_framework import mixins, status, viewsets
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import action
from rest_framework.exceptions import MethodNotAllowed, ValidationError
from rest_framework.filters import BaseFilterBackend, OrderingFilter
@@ -28,6 +28,7 @@
from FTB.ProgramConfiguration import ProgramConfiguration
from FTB.Signatures.CrashInfo import CrashInfo
from server.auth import CheckAppPermission
from server.utils import IPRestrictedTokenAuthentication

from .forms import (
BugzillaTemplateBugForm,
@@ -1017,7 +1018,7 @@ class CrashEntryViewSet(
):
"""API endpoint that allows adding/viewing CrashEntries"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = CrashEntry.objects.all().select_related(
"product", "platform", "os", "client", "tool", "testcase"
)
@@ -1143,7 +1144,7 @@ class BucketViewSet(
):
"""API endpoint that allows viewing Buckets"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = Bucket.objects.all().select_related("bug", "bug__externalType")
serializer_class = BucketSerializer
filter_backends = [
@@ -1430,7 +1431,7 @@ class BugProviderViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
API endpoint that allows listing BugProviders
"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = BugProvider.objects.all()
serializer_class = BugProviderSerializer

@@ -1442,7 +1443,7 @@ class BugzillaTemplateViewSet(
API endpoint that allows viewing BugzillaTemplates
"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = BugzillaTemplate.objects.all()
serializer_class = BugzillaTemplateSerializer

@@ -1452,7 +1453,7 @@ class NotificationViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
API endpoint that allows listing unread Notifications
"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
serializer_class = NotificationSerializer
filter_backends = [
JsonQueryFilterBackend,
@@ -1545,7 +1546,7 @@ def get_query_obj(obj, key=None):


class AbstractDownloadView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
permission_classes = (CheckAppPermission,)

def response(self, file_path, filename, content_type="application/octet-stream"):
@@ -1765,7 +1766,7 @@ class CrashStatsViewSet(viewsets.GenericViewSet):
API endpoint that allows retrieving CrashManager statistics
"""

authentication_classes = (TokenAuthentication, SessionAuthentication)
authentication_classes = (IPRestrictedTokenAuthentication, SessionAuthentication)
queryset = CrashEntry.objects.all()
filter_backends = [
ToolFilterCrashesBackend,
13 changes: 7 additions & 6 deletions server/ec2spotmanager/views.py
Original file line number Diff line number Diff line change
@@ -14,11 +14,12 @@
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.timezone import now, timedelta
from rest_framework import mixins, serializers, status, viewsets
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView

from server.auth import CheckAppPermission
from server.utils import IPRestrictedTokenAuthentication

from .CloudProvider.CloudProvider import (
INSTANCE_STATE,
@@ -917,7 +918,7 @@ def get_labels(self, pool, entries):


class MachineStatusViewSet(APIView):
authentication_classes = (TokenAuthentication,)
authentication_classes = (IPRestrictedTokenAuthentication,)

def get(self, request, *args, **kwargs):
result = {}
@@ -947,7 +948,7 @@ class PoolConfigurationViewSet(
API endpoint that allows viewing PoolConfigurations
"""

authentication_classes = (TokenAuthentication,)
authentication_classes = (IPRestrictedTokenAuthentication,)
permission_classes = (CheckAppPermission,)
queryset = PoolConfiguration.objects.all()
serializer_class = PoolConfigurationSerializer
@@ -964,7 +965,7 @@ def retrieve(self, request, *args, **kwds):


class PoolCycleView(APIView):
authentication_classes = (TokenAuthentication,)
authentication_classes = (IPRestrictedTokenAuthentication,)
permission_classes = (CheckAppPermission,)

def post(self, request, poolid, format=None):
@@ -982,7 +983,7 @@ def post(self, request, poolid, format=None):


class PoolEnableView(APIView):
authentication_classes = (TokenAuthentication,)
authentication_classes = (IPRestrictedTokenAuthentication,)
permission_classes = (CheckAppPermission,)

def post(self, request, poolid, format=None):
@@ -1002,7 +1003,7 @@ def post(self, request, poolid, format=None):


class PoolDisableView(APIView):
authentication_classes = (TokenAuthentication,)
authentication_classes = (IPRestrictedTokenAuthentication,)
permission_classes = (CheckAppPermission,)

def post(self, request, poolid, format=None):
Loading