mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
feat(api): label Postgres connections with application_name per component and alias (#11494)
This commit is contained in:
@@ -9,6 +9,7 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
- Automatic recovery of allowlisted idempotent background tasks whose worker died during a deploy or crash: stuck scan and summary tasks are detected and re-run instead of staying pending forever, with a `reconcile_orphan_tasks` management command for on-demand recovery [(#11416)](https://github.com/prowler-cloud/prowler/pull/11416)
|
||||
- Jira integration no longer creates duplicate issues on a retried send; findings already ticketed are skipped [(#11416)](https://github.com/prowler-cloud/prowler/pull/11416)
|
||||
- DORA compliance framework support [(#11131)](https://github.com/prowler-cloud/prowler/pull/11131)
|
||||
- Label Postgres connections with `application_name="<component>:<alias>"` (component injected per process via `DJANGO_APP_COMPONENT`) so connections are attributable by component in `pg_stat_activity` [(#11494)](https://github.com/prowler-cloud/prowler/pull/11494)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
|
||||
@@ -68,6 +68,15 @@ manage_db_partitions() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Identify this process to Postgres (application_name=<component>:<alias>) so
|
||||
# connections are attributable by component in pg_stat_activity. Web tiers
|
||||
# report "api"; everything else uses the launch subcommand.
|
||||
case "$1" in
|
||||
prod|dev) DJANGO_APP_COMPONENT="api" ;;
|
||||
*) DJANGO_APP_COMPONENT="$1" ;;
|
||||
esac
|
||||
export DJANGO_APP_COMPONENT
|
||||
|
||||
case "$1" in
|
||||
dev)
|
||||
apply_migrations
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
from config.django.base import label_postgres_connections
|
||||
|
||||
|
||||
class TestLabelPostgresConnections:
|
||||
def test_labels_postgres_and_skips_neo4j(self, monkeypatch):
|
||||
monkeypatch.setenv("DJANGO_APP_COMPONENT", "scan")
|
||||
databases = {
|
||||
"default": {"ENGINE": "psqlextra.backend"},
|
||||
"neo4j": {"HOST": "neo4j", "PORT": "7687"},
|
||||
}
|
||||
|
||||
label_postgres_connections(databases)
|
||||
|
||||
assert databases["default"]["OPTIONS"]["application_name"] == "scan:default"
|
||||
assert "OPTIONS" not in databases["neo4j"]
|
||||
|
||||
def test_labels_plain_postgresql_backend(self, monkeypatch):
|
||||
monkeypatch.setenv("DJANGO_APP_COMPONENT", "api")
|
||||
databases = {"saas": {"ENGINE": "django.db.backends.postgresql"}}
|
||||
|
||||
label_postgres_connections(databases)
|
||||
|
||||
assert databases["saas"]["OPTIONS"]["application_name"] == "api:saas"
|
||||
|
||||
def test_defaults_component_to_api_when_unset(self, monkeypatch):
|
||||
monkeypatch.delenv("DJANGO_APP_COMPONENT", raising=False)
|
||||
databases = {"default": {"ENGINE": "psqlextra.backend"}}
|
||||
|
||||
label_postgres_connections(databases)
|
||||
|
||||
assert databases["default"]["OPTIONS"]["application_name"] == "api:default"
|
||||
|
||||
def test_preserves_existing_options(self, monkeypatch):
|
||||
monkeypatch.setenv("DJANGO_APP_COMPONENT", "worker")
|
||||
databases = {
|
||||
"replica": {
|
||||
"ENGINE": "psqlextra.backend",
|
||||
"OPTIONS": {"sslmode": "require"},
|
||||
}
|
||||
}
|
||||
|
||||
label_postgres_connections(databases)
|
||||
|
||||
assert databases["replica"]["OPTIONS"] == {
|
||||
"sslmode": "require",
|
||||
"application_name": "worker:replica",
|
||||
}
|
||||
|
||||
def test_truncates_application_name_to_63_bytes(self, monkeypatch):
|
||||
monkeypatch.setenv("DJANGO_APP_COMPONENT", "c" * 80)
|
||||
databases = {"default": {"ENGINE": "psqlextra.backend"}}
|
||||
|
||||
label_postgres_connections(databases)
|
||||
|
||||
assert len(databases["default"]["OPTIONS"]["application_name"]) == 63
|
||||
@@ -306,3 +306,20 @@ SESSION_COOKIE_SECURE = True
|
||||
ATTACK_PATHS_SCAN_STALE_THRESHOLD_MINUTES = env.int(
|
||||
"ATTACK_PATHS_SCAN_STALE_THRESHOLD_MINUTES", 2880
|
||||
) # 48h
|
||||
|
||||
|
||||
def label_postgres_connections(databases):
|
||||
"""Tag each Postgres connection with ``application_name="<component>:<alias>"``
|
||||
so connections are attributable by component in ``pg_stat_activity`` (and any
|
||||
tooling that surfaces ``application_name``). The component (api / worker /
|
||||
scan / ...) is injected per process by the container entrypoint via
|
||||
``DJANGO_APP_COMPONENT``; the alias distinguishes which pool inside the
|
||||
process owns the connection. The neo4j entry is skipped (not a Postgres
|
||||
backend). Postgres truncates ``application_name`` at 63 bytes.
|
||||
"""
|
||||
component = env.str("DJANGO_APP_COMPONENT", default="api")
|
||||
for alias, config in databases.items():
|
||||
engine = config.get("ENGINE", "")
|
||||
if engine.startswith("psqlextra") or "postgresql" in engine:
|
||||
name = f"{component}:{alias}"[:63]
|
||||
config.setdefault("OPTIONS", {})["application_name"] = name
|
||||
|
||||
@@ -54,6 +54,8 @@ DATABASES = {
|
||||
|
||||
DATABASES["default"] = DATABASES["prowler_user"]
|
||||
|
||||
label_postgres_connections(DATABASES) # noqa: F405
|
||||
|
||||
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = tuple( # noqa: F405
|
||||
render_class
|
||||
for render_class in REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] # noqa: F405
|
||||
|
||||
@@ -58,3 +58,5 @@ DATABASES = {
|
||||
}
|
||||
|
||||
DATABASES["default"] = DATABASES["prowler_user"]
|
||||
|
||||
label_postgres_connections(DATABASES) # noqa: F405
|
||||
|
||||
Reference in New Issue
Block a user