Browse Source

ref(endpoints): Relay directory (#31641)

Marcos Gaeta 3 years ago
parent
commit
219b159e88

+ 29 - 0
src/sentry/api/endpoints/relay/__init__.py

@@ -0,0 +1,29 @@
+from rest_framework import serializers
+
+
+class RelayIdSerializer(serializers.Serializer):
+    relay_id = serializers.RegexField(
+        r"^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$", required=True
+    )
+
+
+from .details import RelayDetailsEndpoint
+from .health_check import RelayHealthCheck
+from .index import RelayIndexEndpoint
+from .project_configs import RelayProjectConfigsEndpoint
+from .project_ids import RelayProjectIdsEndpoint
+from .public_keys import RelayPublicKeysEndpoint
+from .register_challenge import RelayRegisterChallengeEndpoint
+from .register_response import RelayRegisterResponseEndpoint
+
+__all__ = (
+    "RelayDetailsEndpoint",
+    "RelayHealthCheck",
+    "RelayIdSerializer",
+    "RelayIndexEndpoint",
+    "RelayProjectConfigsEndpoint",
+    "RelayProjectIdsEndpoint",
+    "RelayPublicKeysEndpoint",
+    "RelayRegisterChallengeEndpoint",
+    "RelayRegisterResponseEndpoint",
+)

+ 0 - 0
src/sentry/api/endpoints/relay_details.py → src/sentry/api/endpoints/relay/details.py


+ 0 - 0
src/sentry/api/endpoints/relay_healthcheck.py → src/sentry/api/endpoints/relay/health_check.py


+ 0 - 0
src/sentry/api/endpoints/relay_index.py → src/sentry/api/endpoints/relay/index.py


+ 1 - 0
src/sentry/api/endpoints/relay_projectconfigs.py → src/sentry/api/endpoints/relay/project_configs.py

@@ -26,6 +26,7 @@ def _sample_apm():
 class RelayProjectConfigsEndpoint(Endpoint):
     authentication_classes = (RelayAuthentication,)
     permission_classes = (RelayPermission,)
+    enforce_rate_limit = False
 
     def post(self, request: Request) -> Response:
         with start_transaction(

+ 0 - 0
src/sentry/api/endpoints/relay_projectids.py → src/sentry/api/endpoints/relay/project_ids.py


+ 1 - 0
src/sentry/api/endpoints/relay_publickeys.py → src/sentry/api/endpoints/relay/public_keys.py

@@ -10,6 +10,7 @@ from sentry.models import Relay
 class RelayPublicKeysEndpoint(Endpoint):
     authentication_classes = (RelayAuthentication,)
     permission_classes = (RelayPermission,)
+    enforce_rate_limit = False
 
     def post(self, request: Request) -> Response:
         calling_relay = request.relay

+ 2 - 94
src/sentry/api/endpoints/relay_register.py → src/sentry/api/endpoints/relay/register_challenge.py

@@ -1,38 +1,23 @@
 from django.conf import settings
-from django.utils import timezone
 from rest_framework import serializers, status
 from rest_framework.request import Request
 from rest_framework.response import Response
-from sentry_relay import (
-    UnpackErrorSignatureExpired,
-    create_register_challenge,
-    is_version_supported,
-    validate_register_response,
-)
+from sentry_relay import create_register_challenge, is_version_supported
 
 from sentry import options
 from sentry.api.authentication import is_internal_relay, is_static_relay, relay_from_id
 from sentry.api.base import Endpoint
 from sentry.api.serializers import serialize
-from sentry.models import Relay, RelayUsage
 from sentry.relay.utils import get_header_relay_id, get_header_relay_signature
 from sentry.utils import json
 
-
-class RelayIdSerializer(serializers.Serializer):
-    relay_id = serializers.RegexField(
-        r"^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$", required=True
-    )
+from . import RelayIdSerializer
 
 
 class RelayRegisterChallengeSerializer(RelayIdSerializer):
     public_key = serializers.CharField(max_length=64, required=True)
 
 
-class RelayRegisterResponseSerializer(RelayIdSerializer):
-    token = serializers.CharField(required=True)
-
-
 class RelayRegisterChallengeEndpoint(Endpoint):
     authentication_classes = ()
     permission_classes = ()
@@ -107,80 +92,3 @@ class RelayRegisterChallengeEndpoint(Endpoint):
                 )
 
         return Response(serialize(challenge))
-
-
-class RelayRegisterResponseEndpoint(Endpoint):
-    authentication_classes = ()
-    permission_classes = ()
-
-    def post(self, request: Request) -> Response:
-        """
-        Registers a Relay
-        `````````````````
-
-        Registers the relay with the sentry installation.  If a relay boots
-        it will always attempt to invoke this endpoint.
-        """
-
-        try:
-            json_data = json.loads(request.body)
-        except ValueError:
-            return Response({"detail": "No valid json body"}, status=status.HTTP_400_BAD_REQUEST)
-
-        serializer = RelayRegisterResponseSerializer(data=json_data)
-
-        if not serializer.is_valid():
-            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
-
-        sig = get_header_relay_signature(request)
-        if not sig:
-            return Response(
-                {"detail": "Missing relay signature"}, status=status.HTTP_400_BAD_REQUEST
-            )
-
-        secret = options.get("system.secret-key")
-
-        try:
-            validated = validate_register_response(request.body, sig, secret)
-        except UnpackErrorSignatureExpired:
-            return Response({"detail": "Challenge expired"}, status=status.HTTP_401_UNAUTHORIZED)
-        except Exception as exc:
-            return Response(
-                {"detail": str(exc).splitlines()[0]}, status=status.HTTP_400_BAD_REQUEST
-            )
-
-        relay_id = str(validated["relay_id"])
-        version = str(validated["version"])
-        public_key = validated["public_key"]
-
-        if relay_id != get_header_relay_id(request):
-            return Response(
-                {"detail": "relay_id in payload did not match header"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
-
-        relay, static = relay_from_id(request, relay_id)
-
-        if not static:
-            is_internal = is_internal_relay(request, public_key)
-
-            if relay is None:
-                relay = Relay.objects.create(
-                    relay_id=relay_id, public_key=public_key, is_internal=is_internal
-                )
-            else:
-                # update the internal flag in case it is changed
-                relay.is_internal = is_internal
-                relay.save()
-
-            # only update usage for non static relays (static relays should not access the db)
-            try:
-                relay_usage = RelayUsage.objects.get(relay_id=relay_id, version=version)
-            except RelayUsage.DoesNotExist:
-                RelayUsage.objects.create(relay_id=relay_id, version=version, public_key=public_key)
-            else:
-                relay_usage.last_seen = timezone.now()
-                relay_usage.public_key = public_key
-                relay_usage.save()
-
-        return Response(serialize({"relay_id": relay.relay_id}))

+ 96 - 0
src/sentry/api/endpoints/relay/register_response.py

@@ -0,0 +1,96 @@
+from django.utils import timezone
+from rest_framework import serializers, status
+from rest_framework.request import Request
+from rest_framework.response import Response
+from sentry_relay import UnpackErrorSignatureExpired, validate_register_response
+
+from sentry import options
+from sentry.api.authentication import is_internal_relay, relay_from_id
+from sentry.api.base import Endpoint
+from sentry.api.serializers import serialize
+from sentry.models import Relay, RelayUsage
+from sentry.relay.utils import get_header_relay_id, get_header_relay_signature
+from sentry.utils import json
+
+from . import RelayIdSerializer
+
+
+class RelayRegisterResponseSerializer(RelayIdSerializer):
+    token = serializers.CharField(required=True)
+
+
+class RelayRegisterResponseEndpoint(Endpoint):
+    authentication_classes = ()
+    permission_classes = ()
+
+    def post(self, request: Request) -> Response:
+        """
+        Registers a Relay
+        `````````````````
+
+        Registers the relay with the sentry installation.  If a relay boots
+        it will always attempt to invoke this endpoint.
+        """
+
+        try:
+            json_data = json.loads(request.body)
+        except ValueError:
+            return Response({"detail": "No valid json body"}, status=status.HTTP_400_BAD_REQUEST)
+
+        serializer = RelayRegisterResponseSerializer(data=json_data)
+
+        if not serializer.is_valid():
+            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+        sig = get_header_relay_signature(request)
+        if not sig:
+            return Response(
+                {"detail": "Missing relay signature"}, status=status.HTTP_400_BAD_REQUEST
+            )
+
+        secret = options.get("system.secret-key")
+
+        try:
+            validated = validate_register_response(request.body, sig, secret)
+        except UnpackErrorSignatureExpired:
+            return Response({"detail": "Challenge expired"}, status=status.HTTP_401_UNAUTHORIZED)
+        except Exception as exc:
+            return Response(
+                {"detail": str(exc).splitlines()[0]}, status=status.HTTP_400_BAD_REQUEST
+            )
+
+        relay_id = str(validated["relay_id"])
+        version = str(validated["version"])
+        public_key = validated["public_key"]
+
+        if relay_id != get_header_relay_id(request):
+            return Response(
+                {"detail": "relay_id in payload did not match header"},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+
+        relay, static = relay_from_id(request, relay_id)
+
+        if not static:
+            is_internal = is_internal_relay(request, public_key)
+
+            if relay is None:
+                relay = Relay.objects.create(
+                    relay_id=relay_id, public_key=public_key, is_internal=is_internal
+                )
+            else:
+                # update the internal flag in case it is changed
+                relay.is_internal = is_internal
+                relay.save()
+
+            # only update usage for non static relays (static relays should not access the db)
+            try:
+                relay_usage = RelayUsage.objects.get(relay_id=relay_id, version=version)
+            except RelayUsage.DoesNotExist:
+                RelayUsage.objects.create(relay_id=relay_id, version=version, public_key=public_key)
+            else:
+                relay_usage.last_seen = timezone.now()
+                relay_usage.public_key = public_key
+                relay_usage.save()
+
+        return Response(serialize({"relay_id": relay.relay_id}))

+ 10 - 7
src/sentry/api/urls.py

@@ -362,13 +362,16 @@ from .endpoints.project_user_reports import ProjectUserReportsEndpoint
 from .endpoints.project_user_stats import ProjectUserStatsEndpoint
 from .endpoints.project_users import ProjectUsersEndpoint
 from .endpoints.prompts_activity import PromptsActivityEndpoint
-from .endpoints.relay_details import RelayDetailsEndpoint
-from .endpoints.relay_healthcheck import RelayHealthCheck
-from .endpoints.relay_index import RelayIndexEndpoint
-from .endpoints.relay_projectconfigs import RelayProjectConfigsEndpoint
-from .endpoints.relay_projectids import RelayProjectIdsEndpoint
-from .endpoints.relay_publickeys import RelayPublicKeysEndpoint
-from .endpoints.relay_register import RelayRegisterChallengeEndpoint, RelayRegisterResponseEndpoint
+from .endpoints.relay import (
+    RelayDetailsEndpoint,
+    RelayHealthCheck,
+    RelayIndexEndpoint,
+    RelayProjectConfigsEndpoint,
+    RelayProjectIdsEndpoint,
+    RelayPublicKeysEndpoint,
+    RelayRegisterChallengeEndpoint,
+    RelayRegisterResponseEndpoint,
+)
 from .endpoints.release_deploys import ReleaseDeploysEndpoint
 from .endpoints.sentry_app_authorizations import SentryAppAuthorizationsEndpoint
 from .endpoints.sentry_app_avatar import SentryAppAvatarEndpoint