Reputation: 1700
I have a custom middleware in Django to force all the requests to go through a login authentication (with few exceptions like api/token
).
This project allows users to authenticate either via a JWT token or a login in
/admin/login
and all unauthenticated users are redirected to /admin/login.
for authentication.
We deployed the project in Kubernetes and we want Prometheus to scrape /metrics
endpoint but we don't want it to be exposed to unauthenticated users. Prometheus allows for authentication with username
and password
. The thing is that when a request is sent to /metrics
, because of the middleware, the request is redirected to /admin/login
.
So I believe I need to write a custom authentication backend specifically designed for the metrics
endpoint and place it before the other authentication methods.
The request always goes through the middleware first so it will always be redirected to /admin/login
and then will go through the authentication backend.
What is the right way of doing this?
class LoginRequiredMiddleware(MiddlewareMixin):
def __init__(self, get_response):
self.get_response = get_response
def process_request(self, request):
assert hasattr(request, 'user')
path = request.path_info.lstrip('/')
if path == '' or path == '/':
return self.get_response(request)
url_is_exempt = any(url.match(path) for url in EXEMPT_URLS)
if request.user.is_authenticated or url_is_exempt:
# If the user is authenticated OR the URL is in the exempt list
# go to the requested page
return self.get_response(request)
else:
# Trying to access any page as a non authenticated user
return redirect(f"{settings.LOGIN_URL}?next=/{path}")
class MetricsAuthBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
if '/metrics' in request.path:
if username == "username":
#need to fix this to use the hash of the password
pwd_valid = check_password(password, "password")
if pwd_valid:
user = User.objects.get(username=username)
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Upvotes: 4
Views: 492
Reputation: 436
lets try this approach
Adjust the middleware to skip redirecting the /metrics endpoint to /admin/login.
Update your LoginRequiredMiddleware to bypass /metrics authentication checks for Promitheus:
(middleware.py)
from django.conf import settings
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin
import re
class LoginRequiredMiddleware(MiddlewareMixin):
EXEMPT_URLS = [re.compile(settings.LOGIN_URL.lstrip('/'))]
def __init__(self, get_response):
self.get_response = get_response
def process_request(self, request):
path = request.path_info.lstrip('/')
#Bypass the /metrics endpoint to use custom authentication
if path == 'metrics':
return None
# Check for authentication or exempt URLs
if request.user.is_authenticated or any(url.match(path) for url in self.EXEMPT_URLS):
return self.get_response(request)
return redirect(f"{settings.LOGIN_URL}?next=/{path}")
Create a custom authentication backend for /metrics that checks against a username & password, and returns an authenticated user if valid.
create another file called backend.py(you can name it whatever you like)
(backends.py)
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
User = get_user_model()
class MetricsAuthBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
# Only apply this authentication to /metrics endpoint
if request.path == '/metrics':
metrics_username = "username" #replace with your metrics username
metrics_password_hash = "hashed_password" # replace with hashed password
if username == metrics_username and check_password(password, metrics_password_hash):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
In settings.py, make sure this custom backend is included as an authentication method so it can be recognised:
(settings.py)
AUTHENTICATION_BACKENDS = [
'path.to.backends.MetricsAuthBackend',
'django.contrib.auth.backends.ModelBackend', # default backend
]
LOGIN_URL = '/admin/login/'
Upvotes: -1