Add module Qualys Tags V3: catalogue YAML + service + pages /qualys/tagsv3 et /gap

- deploy/qualys_tags_v3.yaml: catalogue 19 DYN + 6 SPEC manuel + prefixes
- app/services/qualys_tags_service.py: list/analyze_gap/create_static/delete via API
- app/routers/qualys_tags.py: routes /qualys/tagsv3 et /gap
- templates: qualys_tagsv3.html (liste) + qualys_tagsv3_gap.html (diff catalogue)
- Route /qualys/tagsv3/create-all-static pour creer les STAT manquants en bulk
- DYN manquants affiches avec QQL copy-paste pour console Qualys (API ne permet pas)
- PyYAML ajoute aux requirements
This commit is contained in:
Pierre & Lumière 2026-04-15 10:14:10 +02:00
parent 105a756008
commit ec7712f0c9
7 changed files with 658 additions and 1 deletions

View File

@ -6,7 +6,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
from .config import APP_NAME, APP_VERSION
from .dependencies import get_current_user, get_user_perms
from .database import SessionLocal, SessionLocalDemo
from .routers import auth, dashboard, servers, settings, users, campaigns, planning, specifics, audit, contacts, qualys, quickwin, referentiel, patching, applications
from .routers import auth, dashboard, servers, settings, users, campaigns, planning, specifics, audit, contacts, qualys, qualys_tags, quickwin, referentiel, patching, applications
class PermissionsMiddleware(BaseHTTPMiddleware):
@ -60,6 +60,7 @@ app.include_router(specifics.router)
app.include_router(audit.router)
app.include_router(contacts.router)
app.include_router(qualys.router)
app.include_router(qualys_tags.router)
app.include_router(quickwin.router)
app.include_router(referentiel.router)
app.include_router(patching.router)

105
app/routers/qualys_tags.py Normal file
View File

@ -0,0 +1,105 @@
"""Router Qualys Tags V3 — liste, gap analysis, creation"""
from fastapi import APIRouter, Request, Depends, Form
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from ..dependencies import get_db, get_current_user, get_user_perms, can_view, can_edit
from ..services.qualys_tags_service import (
list_qualys_tags, analyze_gap, create_static_tag,
delete_tag, generate_console_steps, load_catalog,
)
from ..config import APP_NAME
router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
@router.get("/qualys/tagsv3", response_class=HTMLResponse)
def tags_list_page(request: Request, db=Depends(get_db)):
user = get_current_user(request)
if not user:
return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return RedirectResponse(url="/dashboard")
result = list_qualys_tags(db)
tags = result.get("tags", [])
tags.sort(key=lambda t: (t["type"], t["name"]))
stats = {
"total": len(tags),
"dyn": sum(1 for t in tags if t["type"] == "DYN"),
"stat": sum(1 for t in tags if t["type"] == "STAT"),
}
return templates.TemplateResponse("qualys_tagsv3.html", {
"request": request, "user": user, "app_name": APP_NAME,
"tags": tags, "stats": stats,
"error": result.get("msg") if not result.get("ok") else None,
})
@router.get("/qualys/tagsv3/gap", response_class=HTMLResponse)
def tags_gap_page(request: Request, db=Depends(get_db)):
user = get_current_user(request)
if not user:
return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return RedirectResponse(url="/dashboard")
gap = analyze_gap(db)
console_steps = generate_console_steps(gap.get("missing_dyn", [])) if gap.get("ok") else []
can_modify = can_edit(perms, "qualys")
return templates.TemplateResponse("qualys_tagsv3_gap.html", {
"request": request, "user": user, "app_name": APP_NAME,
"gap": gap, "console_steps": console_steps, "can_modify": can_modify,
})
@router.post("/qualys/tagsv3/create-static")
def tags_create_static(request: Request, db=Depends(get_db),
name: str = Form(...), color: str = Form(""),
description: str = Form("")):
user = get_current_user(request)
if not user:
return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tagsv3/gap?msg=denied")
r = create_static_tag(db, name, color, description)
msg = "created" if r.get("ok") else f"error_{r.get('msg', 'unknown')[:30]}"
return RedirectResponse(url=f"/qualys/tagsv3/gap?msg={msg}", status_code=303)
@router.post("/qualys/tagsv3/create-all-static")
def tags_create_all_static(request: Request, db=Depends(get_db)):
"""Cree tous les tags STAT manquants en un coup."""
user = get_current_user(request)
if not user:
return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tagsv3/gap?msg=denied")
gap = analyze_gap(db)
if not gap.get("ok"):
return RedirectResponse(url="/qualys/tagsv3/gap?msg=qualys_err", status_code=303)
created = failed = 0
for t in gap.get("missing_stat", []):
r = create_static_tag(db, t["name"], t["color"], t.get("description", ""))
if r.get("ok"):
created += 1
else:
failed += 1
return RedirectResponse(
url=f"/qualys/tagsv3/gap?msg=bulk_created_{created}_failed_{failed}",
status_code=303,
)
@router.get("/qualys/tagsv3/catalog", response_class=JSONResponse)
def tags_catalog_json(request: Request, db=Depends(get_db)):
user = get_current_user(request)
if not user:
return JSONResponse({"error": "Non autorise"}, status_code=403)
return load_catalog()

View File

@ -0,0 +1,192 @@
"""Service Qualys Tags V3 — CRUD + analyse gap vs catalogue.
Limitations API Qualys (confirmées par la V3) :
- Tag dynamique : creation impossible via API -> UI console uniquement
- Tag statique : creation + assignment OK via API
- Lecture tous tags (avec ruleType DYNAMIC/STATIC) OK
"""
import os
import requests
from pathlib import Path
from sqlalchemy import text
# Path vers le catalogue V3
CATALOG_PATH = Path(__file__).parent.parent.parent / "deploy" / "qualys_tags_v3.yaml"
def _get_creds(db):
"""Retourne (url, user, pass, proxy) depuis app_secrets."""
from .secrets_service import get_secret
return (
get_secret(db, "qualys_url"),
get_secret(db, "qualys_user"),
get_secret(db, "qualys_pass"),
get_secret(db, "qualys_proxy"),
)
def _qualys_post(db, endpoint, payload, timeout=60):
"""POST sur Qualys QPS REST 2.0."""
url, user, pwd, proxy = _get_creds(db)
if not user:
return {"ok": False, "msg": "Credentials Qualys non configures"}
proxies = {"https": proxy, "http": proxy} if proxy else None
try:
r = requests.post(
f"{url}{endpoint}",
json=payload, auth=(user, pwd),
verify=False, timeout=timeout, proxies=proxies,
headers={"X-Requested-With": "PatchCenter", "Content-Type": "application/json"},
)
return {"ok": r.status_code == 200, "status": r.status_code, "text": r.text}
except Exception as e:
return {"ok": False, "msg": str(e)[:200]}
def _parse_xml_text(text_block, tag):
"""Extrait <tag>valeur</tag> (premier match)."""
import re
m = re.search(f"<{tag}>(.*?)</{tag}>", text_block, re.DOTALL)
return m.group(1).strip() if m else ""
def list_qualys_tags(db):
"""Liste tous les tags Qualys (nom, id, type DYN/STAT, ruleText)."""
# search all tags
payload = {"ServiceRequest": {"preferences": {"limitResults": 1000}}}
r = _qualys_post(db, "/qps/rest/2.0/search/am/tag", payload)
if not r.get("ok"):
return {"ok": False, "msg": r.get("msg") or f"HTTP {r.get('status')}", "tags": []}
tags = []
for block in r["text"].split("<Tag>")[1:]:
block = block.split("</Tag>")[0]
tid = _parse_xml_text(block, "id")
name = _parse_xml_text(block, "name")
rule_type = _parse_xml_text(block, "ruleType")
rule_text = _parse_xml_text(block, "ruleText")
color = _parse_xml_text(block, "color")
if tid and name:
tags.append({
"id": int(tid),
"name": name,
"type": "DYN" if rule_type else "STAT",
"rule_type": rule_type,
"rule_text": rule_text,
"color": color,
})
return {"ok": True, "tags": tags}
def load_catalog():
"""Charge le catalogue YAML V3."""
try:
import yaml
except ImportError:
return {"error": "pyyaml non installe (pip install pyyaml)"}
if not CATALOG_PATH.exists():
return {"error": f"Catalogue introuvable: {CATALOG_PATH}"}
with open(CATALOG_PATH, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def analyze_gap(db):
"""Compare catalogue V3 vs tags Qualys. Retourne missing/extra/mismatch."""
catalog = load_catalog()
if "error" in catalog:
return {"ok": False, "msg": catalog["error"]}
ql = list_qualys_tags(db)
if not ql.get("ok"):
return ql
qualys_by_name = {t["name"]: t for t in ql["tags"]}
catalog_tags = []
for cat_name, cat in catalog.get("categories", {}).items():
for t in cat.get("tags", []) or []:
catalog_tags.append({
"category": cat_name,
"name": t["name"],
"type": t["type"],
"auto": t.get("auto", False),
"qql": t.get("qql", ""),
"color": t.get("color", ""),
"description": t.get("description", ""),
})
missing_dyn = [] # Dans catalogue, pas dans Qualys, type DYN -> creer en console
missing_stat = [] # Dans catalogue, pas dans Qualys, type STAT -> creable via API
mismatch = [] # Present mais type different (DYN vs STAT)
present = [] # OK
for c in catalog_tags:
q = qualys_by_name.get(c["name"])
if not q:
if c["type"] == "DYN":
missing_dyn.append(c)
else:
missing_stat.append(c)
elif c["type"] != q["type"]:
mismatch.append({"catalog": c, "qualys": q})
else:
present.append({"catalog": c, "qualys": q})
# Tags Qualys pas dans le catalogue (pour info)
catalog_names = {c["name"] for c in catalog_tags}
extra = [t for t in ql["tags"] if t["name"] not in catalog_names]
return {
"ok": True,
"present": present,
"missing_dyn": missing_dyn,
"missing_stat": missing_stat,
"mismatch": mismatch,
"extra": extra,
"total_catalog": len(catalog_tags),
"total_qualys": len(ql["tags"]),
}
def create_static_tag(db, name, color="", description=""):
"""Cree un tag statique Qualys via API."""
payload = {
"ServiceRequest": {
"data": {
"Tag": {
"name": name,
"color": color or "#607D8B",
"description": description,
}
}
}
}
r = _qualys_post(db, "/qps/rest/2.0/create/am/tag", payload)
if r.get("ok"):
return {"ok": True, "msg": f"Tag '{name}' cree"}
return {"ok": False, "msg": r.get("msg") or f"HTTP {r.get('status')}: {r.get('text', '')[:200]}"}
def delete_tag(db, tag_id):
"""Supprime un tag Qualys par id."""
r = _qualys_post(db, f"/qps/rest/2.0/delete/am/tag/{tag_id}", {})
return {"ok": r.get("ok"), "msg": r.get("msg") or f"HTTP {r.get('status')}"}
def generate_console_steps(missing_dyn):
"""Retourne un texte avec les etapes console Qualys pour chaque tag DYN manquant."""
steps = []
for t in missing_dyn:
steps.append({
"name": t["name"],
"category": t["category"],
"qql": t["qql"],
"color": t["color"],
"steps": [
"VMDR > Assets > Tags > New Tag",
f"Name: {t['name']}",
f"Color: {t['color']}",
"Cocher 'Create as Dynamic Tag'",
f"Asset Criteria: {t['qql']}",
"Sauvegarder",
],
})
return steps

View File

@ -0,0 +1,44 @@
{% extends 'base.html' %}
{% block title %}Tags V3 - Vue Qualys{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-cyber-accent">Qualys Tags V3 — Vue actuelle</h2>
<div class="flex gap-2">
<a href="/qualys/tagsv3/gap" class="btn-sm bg-cyber-accent text-black">Analyse gap vs nomenclature V3</a>
<a href="/qualys/tags" class="btn-sm bg-cyber-border text-cyber-accent">Tags (vue legacy)</a>
</div>
</div>
{% if error %}
<div class="card p-3 mb-4 border border-red-700 text-red-400">
Erreur Qualys : {{ error }}
</div>
{% endif %}
<div style="display:flex;gap:8px;margin-bottom:16px;">
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold text-cyber-accent">{{ stats.total }}</div><div class="text-xs text-gray-500">Total Qualys</div></div>
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold" style="color:#2196F3">{{ stats.dyn }}</div><div class="text-xs text-gray-500">Dynamiques</div></div>
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold" style="color:#FF9800">{{ stats.stat }}</div><div class="text-xs text-gray-500">Statiques</div></div>
</div>
<div class="card overflow-x-auto">
<table class="w-full table-cyber">
<thead><tr>
<th class="p-2 text-left">Nom</th>
<th class="p-2">Type</th>
<th class="p-2 text-left">Règle (QQL)</th>
<th class="p-2">ID</th>
</tr></thead>
<tbody>
{% for t in tags %}
<tr>
<td class="p-2 font-mono text-sm" style="color:{{ t.color or '#fff' }}">{{ t.name }}</td>
<td class="p-2 text-center"><span class="badge {% if t.type == 'DYN' %}badge-blue{% else %}badge-yellow{% endif %}">{{ t.type }}</span></td>
<td class="p-2 text-xs text-gray-400 font-mono">{{ t.rule_text or '-' }}</td>
<td class="p-2 text-xs text-center text-gray-500">{{ t.id }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,135 @@
{% extends 'base.html' %}
{% block title %}Tags V3 - Gap analysis{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-cyber-accent">Gap nomenclature V3 ↔ Qualys</h2>
<a href="/qualys/tagsv3" class="btn-sm bg-cyber-border text-cyber-accent">← Vue Qualys</a>
</div>
{% if not gap.ok %}
<div class="card p-4 border border-red-700 text-red-400">
Erreur : {{ gap.msg }}
</div>
{% else %}
<!-- KPIs -->
<div style="display:flex;gap:8px;margin-bottom:16px;">
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold text-cyber-green">{{ gap.present|length }}</div><div class="text-xs text-gray-500">Présents OK</div></div>
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold text-cyber-red">{{ gap.missing_dyn|length }}</div><div class="text-xs text-gray-500">DYN manquants (console)</div></div>
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold text-cyber-yellow">{{ gap.missing_stat|length }}</div><div class="text-xs text-gray-500">STAT manquants (API)</div></div>
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold" style="color:#9C27B0">{{ gap.mismatch|length }}</div><div class="text-xs text-gray-500">Type divergent</div></div>
<div class="card p-3 text-center" style="flex:1"><div class="text-2xl font-bold text-gray-400">{{ gap.extra|length }}</div><div class="text-xs text-gray-500">Extra (hors V3)</div></div>
</div>
<!-- DYN manquants -->
{% if gap.missing_dyn %}
<div class="card p-4 mb-4" style="border:1px solid #F44336">
<div class="flex justify-between items-center mb-3">
<h3 class="text-sm font-bold text-cyber-red">🔴 Tags DYN manquants ({{ gap.missing_dyn|length }}) — à créer MANUELLEMENT dans console Qualys</h3>
</div>
<p class="text-xs text-gray-400 mb-3">L'API Qualys ne permet pas de créer des tags dynamiques. Copie les étapes ci-dessous dans VMDR &gt; Assets &gt; Tags &gt; New Tag.</p>
<table class="w-full table-cyber text-sm">
<thead><tr>
<th class="p-2 text-left">Tag</th>
<th class="p-2">Catégorie</th>
<th class="p-2 text-left">QQL à saisir</th>
<th class="p-2">Couleur</th>
</tr></thead>
<tbody>
{% for t in console_steps %}
<tr>
<td class="p-2 font-mono">{{ t.name }}</td>
<td class="p-2 text-center text-xs text-gray-400">{{ t.category }}</td>
<td class="p-2 font-mono text-xs text-cyber-accent" style="user-select:all">{{ t.qql }}</td>
<td class="p-2 text-center"><span style="display:inline-block;width:20px;height:20px;background:{{ t.color }};border-radius:4px;vertical-align:middle"></span> <span class="text-xs text-gray-400">{{ t.color }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- STAT manquants -->
{% if gap.missing_stat %}
<div class="card p-4 mb-4" style="border:1px solid #FF9800">
<div class="flex justify-between items-center mb-3">
<h3 class="text-sm font-bold text-cyber-yellow">🟡 Tags STAT manquants ({{ gap.missing_stat|length }}) — créables via API</h3>
{% if can_modify %}
<form method="POST" action="/qualys/tagsv3/create-all-static" style="display:inline">
<button class="btn-sm bg-cyber-yellow text-black" onclick="return confirm('Créer {{ gap.missing_stat|length }} tags statiques dans Qualys ?')" data-loading="Création des tags...">+ Créer tous</button>
</form>
{% endif %}
</div>
<table class="w-full table-cyber text-sm">
<thead><tr>
<th class="p-2 text-left">Tag</th>
<th class="p-2">Catégorie</th>
<th class="p-2 text-left">Description</th>
<th class="p-2">Couleur</th>
{% if can_modify %}<th class="p-2">Action</th>{% endif %}
</tr></thead>
<tbody>
{% for t in gap.missing_stat %}
<tr>
<td class="p-2 font-mono">{{ t.name }}</td>
<td class="p-2 text-center text-xs text-gray-400">{{ t.category }}</td>
<td class="p-2 text-xs">{{ t.description or '-' }}</td>
<td class="p-2 text-center"><span style="display:inline-block;width:20px;height:20px;background:{{ t.color }};border-radius:4px;vertical-align:middle"></span></td>
{% if can_modify %}
<td class="p-2 text-center">
<form method="POST" action="/qualys/tagsv3/create-static" style="display:inline">
<input type="hidden" name="name" value="{{ t.name }}">
<input type="hidden" name="color" value="{{ t.color }}">
<input type="hidden" name="description" value="{{ t.description }}">
<button class="btn-sm bg-cyber-yellow text-black text-xs">Créer</button>
</form>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Mismatch -->
{% if gap.mismatch %}
<div class="card p-4 mb-4" style="border:1px solid #9C27B0">
<h3 class="text-sm font-bold" style="color:#9C27B0">⚠ Type divergent ({{ gap.mismatch|length }})</h3>
<p class="text-xs text-gray-400 mb-2">Le type (DYN/STAT) dans Qualys ne correspond pas au catalogue V3.</p>
<table class="w-full table-cyber text-sm">
<thead><tr><th class="p-2 text-left">Tag</th><th class="p-2">V3</th><th class="p-2">Qualys</th></tr></thead>
<tbody>
{% for m in gap.mismatch %}
<tr><td class="p-2 font-mono">{{ m.catalog.name }}</td><td class="p-2 text-center">{{ m.catalog.type }}</td><td class="p-2 text-center">{{ m.qualys.type }}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Présents OK -->
<details class="card p-3 mb-4">
<summary class="cursor-pointer text-sm font-bold text-cyber-green">✓ Tags V3 présents ({{ gap.present|length }})</summary>
<table class="w-full table-cyber text-sm mt-2">
<thead><tr><th class="p-2 text-left">Tag</th><th class="p-2">Type</th></tr></thead>
<tbody>
{% for p in gap.present %}
<tr><td class="p-2 font-mono">{{ p.catalog.name }}</td><td class="p-2 text-center"><span class="badge {% if p.catalog.type == 'DYN' %}badge-blue{% else %}badge-yellow{% endif %}">{{ p.catalog.type }}</span></td></tr>
{% endfor %}
</tbody>
</table>
</details>
<!-- Extras -->
{% if gap.extra %}
<details class="card p-3">
<summary class="cursor-pointer text-sm font-bold text-gray-400">Extras Qualys (non-V3) ({{ gap.extra|length }})</summary>
<div class="text-xs text-gray-500 mt-2">
{% for t in gap.extra %}<span class="mr-2">{{ t.name }}</span>{% endfor %}
</div>
</details>
{% endif %}
{% endif %}
{% endblock %}

179
deploy/qualys_tags_v3.yaml Normal file
View File

@ -0,0 +1,179 @@
# Catalogue des tags Qualys V3 SANEF
# Source : SANEF DSI / Sécurité Opérationnelle — Plan d'action Qualys V3 (Mars 2026)
#
# type: DYN (dynamic — création console web Qualys UNIQUEMENT)
# STAT (static — création + assignation via API OK)
# auto: True = entierement automatisable (Tag Rule ou script)
# False = necessite decision humaine
categories:
OS:
description: "Système d'exploitation — dynamique sur operatingSystem"
tags:
- name: OS-LIN
type: DYN
auto: true
qql: 'operatingSystem.category1: "Linux"'
color: "#4CAF50"
- name: OS-WIN
type: DYN
auto: true
qql: 'operatingSystem.category1: "Windows"'
color: "#2196F3"
- name: OS-WIN-SRV
type: DYN
auto: true
qql: 'operatingSystem: "Windows Server"'
color: "#1976D2"
- name: OS-ESX
type: DYN
auto: true
qql: 'operatingSystem: "ESXi"'
color: "#9C27B0"
ENV:
description: "Environnement — dynamique sur hostname position 2"
tags:
- name: ENV-PRD
type: DYN
auto: true
qql: 'name: "vp" OR name: "sp" OR name: "lp" OR name: "ls-"'
color: "#F44336"
- name: ENV-REC
type: DYN
auto: true
qql: 'name: "vr" OR name: "sr" OR name: "lr"'
color: "#FF9800"
- name: ENV-PPR
type: DYN
auto: true
qql: 'name: "vi" OR name: "si" OR name: "vo"'
color: "#FFC107"
- name: ENV-TST
type: DYN
auto: true
qql: 'name: "vv" OR name: "vt"'
color: "#CDDC39"
- name: ENV-DEV
type: DYN
auto: true
qql: 'name: "vd" OR name: "sd"'
color: "#8BC34A"
POS:
description: "Périmètre / Domaine — dynamique sur hostname positions 2-N"
tags:
- name: POS-FL
type: DYN
auto: true
qql: 'name: "*bot" OR name: "*boo" OR name: "*boc" OR name: "*afl" OR name: "*sup"'
color: "#009688"
- name: POS-INF
type: DYN
auto: true
qql: 'name: "*dsi" OR name: "*cyb" OR name: "*vsa" OR name: "*iad" OR name: "*bur" OR name: "*aii" OR name: "*ecm" OR name: "*log" OR name: "*vid" OR name: "*gaw" OR name: "*bck" OR name: "*ngw" OR name: "*pct" OR name: "*pix" OR name: "*sim" OR name: "*nms" OR name: "*ges" OR name: "*mon"'
color: "#3F51B5"
- name: POS-PEA
type: DYN
auto: true
qql: 'name: "*pea" OR name: "*osa" OR name: "*svp" OR name: "*adv" OR name: "*rpa" OR name: "*rpn" OR name: "ls-"'
color: "#673AB7"
- name: POS-TRA
type: DYN
auto: true
qql: 'name: "*ame" OR name: "*tra" OR name: "*dai" OR name: "*pat" OR name: "*rau" OR name: "*dep" OR name: "*exp" OR name: "*sig" OR name: "*air"'
color: "#E91E63"
- name: POS-BI
type: DYN
auto: true
qql: 'name: "*dec" OR name: "*sas" OR name: "*bip" OR name: "*apt" OR name: "*pbi" OR name: "*rep"'
color: "#FF5722"
- name: POS-GES
type: DYN
auto: true
qql: 'name: "*int" OR name: "*agt" OR name: "*pin" OR name: "*ech"'
color: "#795548"
- name: POS-DMZ
type: DYN
auto: true
qql: 'name: "*ssi"'
color: "#607D8B"
EQT:
description: "Type equipement — position 1"
tags:
- name: EQT-VIR
type: DYN
auto: true
qql: 'name: "v"'
color: "#00BCD4"
- name: EQT-SRV
type: DYN
auto: true
qql: 'name: "l" OR name: "s"'
color: "#03A9F4"
- name: EQT-SWI
type: DYN
auto: true
qql: 'name: "n"'
color: "#4DD0E1"
SPEC_AUTO:
description: "Tags spécifiques automatisables"
tags:
- name: TAG-OBS
type: DYN
auto: true
qql: 'operatingSystem: "Windows Server 2008" OR operatingSystem: "Windows Server 2012" OR operatingSystem: "CentOS release 6" OR operatingSystem: "Red Hat Enterprise Linux Server release 6"'
color: "#B71C1C"
- name: TAG-EMV
type: DYN
auto: true
qql: 'name: "*emv" OR name: "*pci"'
color: "#D500F9"
SPEC_MANUAL:
description: "Tags spécifiques non automatisables (decision humaine)"
tags:
- name: TAG-SED
type: STAT
auto: false
description: "Securite Exposition Directe — IP publique / NAT direct"
color: "#C62828"
- name: TAG-SEI
type: STAT
auto: false
description: "Securite Exposition Indirecte — derriere frontal"
color: "#EF6C00"
- name: TAG-DEC
type: STAT
auto: false
description: "Decommissionnement en cours"
color: "#6D4C41"
- name: TAG-INT
type: STAT
auto: false
description: "Integration / Implémentation en cours"
color: "#FDD835"
- name: TAG-SIC
type: STAT
auto: false
description: "Zone SIC — Systeme Information Classifie"
color: "#1A237E"
- name: TAG-SIA
type: STAT
auto: false
description: "Zone SIA — Systeme Information Administration"
color: "#283593"
PREFIXES_MANUAL:
description: "Prefixes statiques pour tags nominatifs (crees a la demande via API)"
prefixes:
- prefix: APP-
description: "Application hebergee — APP-SAT, APP-JIRA, APP-GLPI..."
- prefix: BDD-
description: "Type de base de donnees — BDD-ORA, BDD-PG, BDD-SQL..."
- prefix: VRF-
description: "VRF reseau — VRF-TRAFIC, VRF-EMV..."
- prefix: MID-
description: "Middleware — MID-TOMCAT, MID-HAPROXY..."

View File

@ -34,6 +34,7 @@ PyNaCl==1.6.2
python-jose==3.5.0
python-multipart==0.0.26
python-pptx==1.0.2
PyYAML==6.0.2
reportlab==4.4.10
requests==2.33.1
rsa==4.9.1