Skip to content

Commit

Permalink
policies/reputation: save to database directly
Browse files Browse the repository at this point in the history
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
  • Loading branch information
rissson committed Jun 11, 2024
1 parent 9379b3d commit 6f1758a
Show file tree
Hide file tree
Showing 6 changed files with 17 additions and 72 deletions.
1 change: 0 additions & 1 deletion authentik/lib/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ cache:
timeout: 300
timeout_flows: 300
timeout_policies: 300
timeout_reputation: 300

# channel:
# url: ""
Expand Down
2 changes: 0 additions & 2 deletions authentik/policies/reputation/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from authentik.blueprints.apps import ManagedAppConfig

CACHE_KEY_PREFIX = "goauthentik.io/policies/reputation/scores/"


class AuthentikPolicyReputationConfig(ManagedAppConfig):
"""Authentik reputation app config"""
Expand Down
5 changes: 5 additions & 0 deletions authentik/policies/reputation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,8 @@ class Meta:
verbose_name = _("Reputation Score")
verbose_name_plural = _("Reputation Scores")
unique_together = ("identifier", "ip")
indexes = [
models.Index(fields=["identifier"]),
models.Index(fields=["ip"]),
models.Index(fields=["ip", "identifier"]),
]
30 changes: 12 additions & 18 deletions authentik/policies/reputation/signals.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,34 @@
"""authentik reputation request signals"""

from django.contrib.auth.signals import user_logged_in
from django.core.cache import cache
from django.dispatch import receiver
from django.http import HttpRequest
from structlog.stdlib import get_logger

from authentik.core.signals import login_failed
from authentik.lib.config import CONFIG
from authentik.policies.reputation.apps import CACHE_KEY_PREFIX
from authentik.policies.reputation.tasks import save_reputation
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
from authentik.policies.reputation.models import Reputation
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.identification.signals import identification_failed

LOGGER = get_logger()
CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_reputation")


def update_score(request: HttpRequest, identifier: str, amount: int):
"""Update score for IP and User"""
remote_ip = ClientIPMiddleware.get_client_ip(request)

try:
# We only update the cache here, as its faster than writing to the DB
score = cache.get_or_set(
CACHE_KEY_PREFIX + remote_ip + "/" + identifier,
{"ip": remote_ip, "identifier": identifier, "score": 0},
CACHE_TIMEOUT,
)
score["score"] += amount
cache.set(CACHE_KEY_PREFIX + remote_ip + "/" + identifier, score)
except ValueError as exc:
LOGGER.warning("failed to set reputation", exc=exc)

Reputation.objects.update_or_create(
ip=remote_ip,
identifier=identifier,
defaults={
"score": amount,
"ip_geo_data": GEOIP_CONTEXT_PROCESSOR.city_dict(remote_ip) or {},
"ip_asn_data": ASN_CONTEXT_PROCESSOR.asn_dict(remote_ip) or {},
}
)
LOGGER.debug("Updated score", amount=amount, for_user=identifier, for_ip=remote_ip)
save_reputation.delay()


@receiver(login_failed)
Expand Down
32 changes: 0 additions & 32 deletions authentik/policies/reputation/tasks.py

This file was deleted.

19 changes: 0 additions & 19 deletions authentik/policies/reputation/tests.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""test reputation signals and policy"""

from django.core.cache import cache
from django.test import RequestFactory, TestCase

from authentik.core.models import User
from authentik.lib.generators import generate_id
from authentik.policies.reputation.api import ReputationPolicySerializer
from authentik.policies.reputation.apps import CACHE_KEY_PREFIX
from authentik.policies.reputation.models import Reputation, ReputationPolicy
from authentik.policies.reputation.tasks import save_reputation
from authentik.policies.types import PolicyRequest
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import authenticate
Expand All @@ -22,8 +19,6 @@ def setUp(self):
self.request = self.request_factory.get("/")
self.test_ip = "127.0.0.1"
self.test_username = "test"
keys = cache.keys(CACHE_KEY_PREFIX + "*")
cache.delete_many(keys)
# We need a user for the one-to-one in userreputation
self.user = User.objects.create(username=self.test_username)
self.backends = [BACKEND_INBUILT]
Expand All @@ -34,13 +29,6 @@ def test_ip_reputation(self):
authenticate(
self.request, self.backends, username=self.test_username, password=self.test_username
)
# Test value in cache
self.assertEqual(
cache.get(CACHE_KEY_PREFIX + self.test_ip + "/" + self.test_username),
{"ip": "127.0.0.1", "identifier": "test", "score": -1},
)
# Save cache and check db values
save_reputation.delay().get()
self.assertEqual(Reputation.objects.get(ip=self.test_ip).score, -1)

def test_user_reputation(self):
Expand All @@ -49,13 +37,6 @@ def test_user_reputation(self):
authenticate(
self.request, self.backends, username=self.test_username, password=self.test_username
)
# Test value in cache
self.assertEqual(
cache.get(CACHE_KEY_PREFIX + self.test_ip + "/" + self.test_username),
{"ip": "127.0.0.1", "identifier": "test", "score": -1},
)
# Save cache and check db values
save_reputation.delay().get()
self.assertEqual(Reputation.objects.get(identifier=self.test_username).score, -1)

def test_policy(self):
Expand Down

0 comments on commit 6f1758a

Please sign in to comment.