-
Notifications
You must be signed in to change notification settings - Fork 1
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
Dbp 1037 setup privacyidea monitring #140
base: master
Are you sure you want to change the base?
Changes from all commits
85fd1d4
21af7db
afb1e17
953d135
2a8ba9d
09fe215
18b5bb2
0ff73d7
68655a0
a623fa5
c441059
2c1e069
bd99724
e2ac55c
59e53fa
fcbbb7c
31b77ee
beb8db7
403203d
d668655
fc35b9f
3982935
2401551
df97303
4860c27
b0246c0
4cd29ef
27482a7
991433c
36e5671
3fb83cd
14062ac
6eba793
aa38b41
9153679
c3081d1
9047b07
b9fe1ae
e8fb260
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,3 +63,4 @@ | |
path: "/tmp/certificate_sync_key_*" | ||
state: absent | ||
delegate_to: localhost | ||
ignore_errors: true | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,9 @@ | |
group: www-data | ||
mode: '0755' | ||
|
||
# - name: Execute the privacyidea_data_import_and_cleanup.sh script | ||
# shell: /etc/privacyidea/privacyidea_data_import_and_cleanup.sh | ||
# args: | ||
# executable: /bin/bash | ||
#- name: Execute the privacyidea_data_import_and_cleanup.sh script | ||
# shell: /etc/privacyidea/privacyidea_data_import_and_cleanup.sh | ||
# args: | ||
# executable: /bin/bash | ||
# notify: restart uwsgi | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason for commenting this out? There is a when condition in the main.yml so it would only be executed if privacyidea_execute_data_import is true. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,7 @@ | |
state: present | ||
update_cache: yes | ||
|
||
- name: Install prometheus_client in PrivacyIDEA | ||
ansible.builtin.command: | ||
cmd: sudo -H /opt/privacyidea/virtualenv/bin/pip install prometheus_client | ||
become: true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May be the ansible pip module could be more elegant here? I think it supports virtualenvs. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
|
||
--- | ||
- name: Deploy custom exporter script | ||
template: | ||
src: custom_exporter.py.j2 | ||
dest: /etc/privacyidea/custom_exporter.py | ||
owner: www-data | ||
group: www-data | ||
mode: '0775' | ||
|
||
- name: Create uWSGI configuration for custom exporter | ||
template: | ||
src: privacyidea-exporter.ini.j2 | ||
dest: /etc/uwsgi/apps-available/privacyidea-exporter.ini | ||
owner: www-data | ||
group: www-data | ||
mode: '0644' | ||
|
||
- name: Link uWSGI config for custom exporter | ||
file: | ||
src: /etc/uwsgi/apps-available/privacyidea-exporter.ini | ||
dest: /etc/uwsgi/apps-enabled/privacyidea-exporter.ini | ||
state: link | ||
|
||
- name: Deploy custom exporter Systemd service | ||
template: | ||
src: privacyidea-exporter.service.j2 | ||
dest: /etc/systemd/system/privacyidea-exporter.service | ||
owner: www-data | ||
group: www-data | ||
mode: '0644' | ||
|
||
- name: Reload systemd daemon | ||
systemd: | ||
daemon_reload: true |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import os | ||
import sys | ||
import socket | ||
import time | ||
import logging | ||
from prometheus_client import start_http_server, Gauge, Counter, generate_latest | ||
from privacyidea.app import create_app | ||
from flask import Flask, Response, request | ||
import sqlalchemy | ||
from sqlalchemy.pool import QueuePool | ||
from functools import wraps | ||
|
||
# Set up logging | ||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | ||
|
||
# Get the hostname | ||
hostname = socket.gethostname() | ||
|
||
config_name = "{{ config_name }}" | ||
debug_mode = {{ debug_mode }} | ||
|
||
# Create the PrivacyIDEA application | ||
application = create_app(config_name=config_name, config_file="/etc/privacyidea/pi.cfg") | ||
application.debug = debug_mode | ||
|
||
# Get credentials from environment variables | ||
EXPORTER_USERNAME = os.environ.get('EXPORTER_USERNAME') | ||
EXPORTER_PASSWORD = os.environ.get('EXPORTER_PASSWORD') | ||
|
||
def check_auth(username, password): | ||
"""Check if a username/password combination is valid.""" | ||
return username == EXPORTER_USERNAME and password == EXPORTER_PASSWORD | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this could potentially be vulnerable for timing attacks. |
||
|
||
def authenticate(): | ||
"""Sends a 401 response that enables basic auth""" | ||
return Response( | ||
'Could not verify your access level for that URL.\n' | ||
'You have to login with proper credentials', 401, | ||
{'WWW-Authenticate': 'Basic realm="Login Required"'}) | ||
|
||
def requires_auth(f): | ||
@wraps(f) | ||
def decorated(*args, **kwargs): | ||
auth = request.authorization | ||
if not auth or not check_auth(auth.username, auth.password): | ||
return authenticate() | ||
return f(*args, **kwargs) | ||
return decorated | ||
|
||
# Define the /metrics endpoint function outside the PrivacyIDEAExporter class | ||
@requires_auth | ||
def metrics(): | ||
exporter.collect_metrics() | ||
return Response(generate_latest(), mimetype="text/plain") | ||
|
||
class PrivacyIDEAExporter: | ||
def __init__(self, app, port=9101): | ||
self.app = app | ||
self.port = port | ||
self.flask_app = Flask(__name__) | ||
|
||
# Define Prometheus metrics | ||
self.auth_success = Gauge('privacyidea_auth_success_total', 'Total successful authentications') | ||
self.auth_failure = Gauge('privacyidea_auth_failure_total', 'Total failed authentications') | ||
self.tokens_total = Gauge('privacyidea_tokens_total', 'Total number of tokens') | ||
self.hardware_tokens = Gauge('privacyidea_hardware_tokens', 'Number of hardware tokens') | ||
self.software_tokens = Gauge('privacyidea_software_tokens', 'Number of software tokens') | ||
self.unassigned_hardware_tokens = Gauge('privacyidea_unassigned_hardware_tokens', 'Number of unassigned hardware tokens') | ||
self.assigned_tokens = Gauge('privacyidea_assigned_tokens', 'Number of assigned tokens') | ||
self.new_users = Gauge('privacyidea_new_users', 'Number of new users') | ||
self.token_init = Gauge('privacyidea_token_init', 'Number of token initializations') | ||
self.token_assign = Gauge('privacyidea_token_assign', 'Number of token assignments') | ||
self.token_unassign = Gauge('privacyidea_token_unassign', 'Number of token unassignments') | ||
|
||
# Create a connection pool | ||
self.engine = sqlalchemy.create_engine( | ||
application.config['SQLALCHEMY_DATABASE_URI'], | ||
poolclass=QueuePool, | ||
pool_size=5, | ||
max_overflow=10 | ||
) | ||
|
||
def fetch_latest_stat(self, connection, stats_key): | ||
query = """ | ||
SELECT stats_value | ||
FROM monitoringstats | ||
WHERE stats_key = :key | ||
ORDER BY timestamp DESC | ||
LIMIT 1 | ||
""" | ||
result = connection.execute(sqlalchemy.text(query), {"key": stats_key}) | ||
return result.scalar() or 0 | ||
|
||
def collect_metrics(self): | ||
with self.app.app_context(): | ||
try: | ||
with self.engine.connect() as connection: | ||
# Fetch and set metrics | ||
self.tokens_total.set(self.fetch_latest_stat(connection, 'total_tokens')) | ||
self.hardware_tokens.set(self.fetch_latest_stat(connection, 'hardware_tokens')) | ||
self.software_tokens.set(self.fetch_latest_stat(connection, 'software_tokens')) | ||
self.unassigned_hardware_tokens.set(self.fetch_latest_stat(connection, 'unassigned_hardware_tokens')) | ||
self.assigned_tokens.set(self.fetch_latest_stat(connection, 'assigned_tokens')) | ||
self.auth_success.set(self.fetch_latest_stat(connection, 'successful_auth_counter')) | ||
self.auth_failure.set(self.fetch_latest_stat(connection, 'failed_auth_counter')) | ||
self.new_users.set(self.fetch_latest_stat(connection, 'new_users_counter')) | ||
self.token_init.set(self.fetch_latest_stat(connection, 'token_init_counter')) | ||
self.token_assign.set(self.fetch_latest_stat(connection, 'token_assign_counter')) | ||
self.token_unassign.set(self.fetch_latest_stat(connection, 'token_unassign_counter')) | ||
|
||
logging.info("Metrics collected successfully") | ||
except Exception as e: | ||
logging.error(f"Error fetching metrics: {e}", exc_info=True) | ||
|
||
exporter = PrivacyIDEAExporter(application) | ||
application.add_url_rule('/metrics', 'metrics', metrics) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for ignoring errors here?