new features
This commit is contained in:
@@ -2,6 +2,8 @@ import requests
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import execute_values
|
from psycopg2.extras import execute_values
|
||||||
|
|
||||||
@@ -9,6 +11,18 @@ JENKINS_URL = "https://jenkins.lmru.tech"
|
|||||||
AUTH_USER = "lm-sa-devsecops"
|
AUTH_USER = "lm-sa-devsecops"
|
||||||
AUTH_TOKEN = "11b0f9ff6c986b18f0c7fa6e8ae769b66c"
|
AUTH_TOKEN = "11b0f9ff6c986b18f0c7fa6e8ae769b66c"
|
||||||
|
|
||||||
|
VAULT_URL = "https://vault.lmru.tech"
|
||||||
|
|
||||||
|
ARTIFACTORY_URL = "https://art.lmru.tech"
|
||||||
|
|
||||||
|
SONARQUBE_URL = "https://sonar.lmru.tech"
|
||||||
|
|
||||||
|
CROWD_URL = "https://crowd.lmru.tech"
|
||||||
|
|
||||||
|
GHE_URL = "https://github.lmru.tech"
|
||||||
|
|
||||||
|
ARGOCD_URL = "https://argocd.devops.lmru.tech/"
|
||||||
|
|
||||||
NVD_API_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
|
NVD_API_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
|
||||||
NVD_API_KEY = "aa054ea4-4a99-4f16-b5ed-1f77b5193d80"
|
NVD_API_KEY = "aa054ea4-4a99-4f16-b5ed-1f77b5193d80"
|
||||||
|
|
||||||
@@ -44,6 +58,88 @@ def get_jenkins_plugins():
|
|||||||
plugin_list.append((shortName, version))
|
plugin_list.append((shortName, version))
|
||||||
return plugin_list
|
return plugin_list
|
||||||
|
|
||||||
|
def get_vault_version():
|
||||||
|
"""Получить версию Vault."""
|
||||||
|
# Предполагаем, что Vault доступен по https://vault.lmru.tech/v1/sys/health
|
||||||
|
# и возвращает JSON с полем "version"
|
||||||
|
url = f"{VAULT_URL}/v1/sys/health"
|
||||||
|
resp = requests.get(url, verify=False)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
vault_version = data.get("version")
|
||||||
|
return vault_version
|
||||||
|
|
||||||
|
def get_artifactory_version():
|
||||||
|
# Artifactory version endpoint: /api/system/version
|
||||||
|
url = f"{ARTIFACTORY_URL}/api/system/version"
|
||||||
|
resp = requests.get(url, verify=False)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
artifactory_version = data.get("version")
|
||||||
|
return artifactory_version
|
||||||
|
|
||||||
|
def get_sonarqube_version():
|
||||||
|
# Эндпоинт SonarQube: /api/server/version
|
||||||
|
url = f"{SONARQUBE_URL}/api/server/version"
|
||||||
|
resp = requests.get(url, verify=False)
|
||||||
|
resp.raise_for_status()
|
||||||
|
# Версия приходит как простой текст (str)
|
||||||
|
sonarqube_version = resp.text.strip()
|
||||||
|
return sonarqube_version
|
||||||
|
|
||||||
|
def get_crowd_version():
|
||||||
|
url = f"{CROWD_URL}/crowd/console/login.action"
|
||||||
|
resp = requests.get(url, verify=False)
|
||||||
|
resp.raise_for_status()
|
||||||
|
html = resp.text
|
||||||
|
|
||||||
|
# Ищем значение атрибута value у input с id="crowd-version"
|
||||||
|
match_input = re.search(r'<input[^>]*id="crowd-version"[^>]*value="([^"]+)"', html)
|
||||||
|
if not match_input:
|
||||||
|
print("Could not find the crowd-version input field.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
value_str = match_input.group(1) # например: 'Version: 4.2.2 (Build:#1581 - 2020-11-19) '
|
||||||
|
# Заменим на пробел
|
||||||
|
value_str = value_str.replace(' ', ' ')
|
||||||
|
|
||||||
|
# Ищем версию после слова Version:
|
||||||
|
match_version = re.search(r'Version:\s*([0-9]+\.[0-9]+\.[0-9]+)', value_str)
|
||||||
|
if match_version:
|
||||||
|
return match_version.group(1)
|
||||||
|
else:
|
||||||
|
print("Could not extract Crowd version from the value string.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_ghe_version():
|
||||||
|
url = f"{GHE_URL}/api/v3/meta"
|
||||||
|
resp = requests.get(url, verify=False)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
ghe_version = data.get("installed_version")
|
||||||
|
if ghe_version:
|
||||||
|
return ghe_version
|
||||||
|
else:
|
||||||
|
print("Could not extract GitHub Enterprise version from the /meta endpoint.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_argocd_version():
|
||||||
|
url = f"{ARGOCD_URL}/api/version"
|
||||||
|
resp = requests.get(url, verify=False)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
argocd_version = data.get("Version")
|
||||||
|
if argocd_version:
|
||||||
|
# Удалим префикс 'v'
|
||||||
|
argocd_version = argocd_version.lstrip('v')
|
||||||
|
# Если есть суффикс вида +коммита, уберём всё после +
|
||||||
|
if '+' in argocd_version:
|
||||||
|
argocd_version = argocd_version.split('+')[0]
|
||||||
|
return argocd_version
|
||||||
|
else:
|
||||||
|
print("Could not determine ArgoCD version from /api/version endpoint.")
|
||||||
|
return None
|
||||||
|
|
||||||
def fetch_cves_by_cpe(cpe_name, max_retries=3, delay=5):
|
def fetch_cves_by_cpe(cpe_name, max_retries=3, delay=5):
|
||||||
"""
|
"""
|
||||||
Запросить уязвимости из NVD по cpeName.
|
Запросить уязвимости из NVD по cpeName.
|
||||||
@@ -84,30 +180,20 @@ def fetch_cves_by_cpe(cpe_name, max_retries=3, delay=5):
|
|||||||
print("All attempts failed unexpectedly. Exiting.")
|
print("All attempts failed unexpectedly. Exiting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def insert_vulnerabilities(conn, name, version, vulnerabilities):
|
def insert_vulnerabilities(conn, component, name, version, vulnerabilities):
|
||||||
"""
|
"""
|
||||||
Вставить или обновить информацию об уязвимостях в PostgreSQL.
|
Вставить или обновить информацию об уязвимостях в PostgreSQL.
|
||||||
|
component - строка, например 'jenkins' или 'vault'.
|
||||||
Если нет уязвимостей:
|
|
||||||
- вставляем/обновляем запись с cve_id='NO_VULN'.
|
|
||||||
|
|
||||||
Если уязвимости появились:
|
|
||||||
- удаляем запись 'NO_VULN' (если была),
|
|
||||||
- вставляем все уязвимости с UPSERT.
|
|
||||||
|
|
||||||
Используем ON CONFLICT DO UPDATE для актуализации данных.
|
|
||||||
"""
|
"""
|
||||||
if vulnerabilities:
|
if vulnerabilities:
|
||||||
# Удаляем запись NO_VULN, если есть
|
|
||||||
delete_query = """
|
delete_query = """
|
||||||
DELETE FROM software_vulnerabilities
|
DELETE FROM software_vulnerabilities
|
||||||
WHERE name = %s AND version = %s AND cve_id = 'NO_VULN';
|
WHERE component = %s AND name = %s AND version = %s AND cve_id = 'NO_VULN';
|
||||||
"""
|
"""
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(delete_query, (name, version))
|
cur.execute(delete_query, (component, name, version))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Вставляем реальные уязвимости
|
|
||||||
records = []
|
records = []
|
||||||
for v in vulnerabilities:
|
for v in vulnerabilities:
|
||||||
cve = v.get("cve", {})
|
cve = v.get("cve", {})
|
||||||
@@ -126,12 +212,13 @@ def insert_vulnerabilities(conn, name, version, vulnerabilities):
|
|||||||
cvss_data = m.get("cvssData", {})
|
cvss_data = m.get("cvssData", {})
|
||||||
severity = cvss_data.get("baseSeverity", None)
|
severity = cvss_data.get("baseSeverity", None)
|
||||||
|
|
||||||
records.append((name, version, cve_id, desc_en, severity, published_date, last_modified, True))
|
# Добавляем component как первый элемент кортежа
|
||||||
|
records.append((component, name, version, cve_id, desc_en, severity, published_date, last_modified, True))
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
INSERT INTO software_vulnerabilities (name, version, cve_id, description, severity, published_date, last_modified, has_vulnerabilities)
|
INSERT INTO software_vulnerabilities (component, name, version, cve_id, description, severity, published_date, last_modified, has_vulnerabilities)
|
||||||
VALUES %s
|
VALUES %s
|
||||||
ON CONFLICT (name, version, cve_id) DO UPDATE SET
|
ON CONFLICT (component, name, version, cve_id) DO UPDATE SET
|
||||||
description = EXCLUDED.description,
|
description = EXCLUDED.description,
|
||||||
severity = EXCLUDED.severity,
|
severity = EXCLUDED.severity,
|
||||||
published_date = EXCLUDED.published_date,
|
published_date = EXCLUDED.published_date,
|
||||||
@@ -142,13 +229,11 @@ def insert_vulnerabilities(conn, name, version, vulnerabilities):
|
|||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
execute_values(cur, query, records)
|
execute_values(cur, query, records)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Нет уязвимостей - вставляем/обновляем NO_VULN
|
|
||||||
query = """
|
query = """
|
||||||
INSERT INTO software_vulnerabilities (name, version, cve_id, has_vulnerabilities)
|
INSERT INTO software_vulnerabilities (component, name, version, cve_id, has_vulnerabilities)
|
||||||
VALUES (%s, %s, 'NO_VULN', FALSE)
|
VALUES (%s, %s, %s, 'NO_VULN', FALSE)
|
||||||
ON CONFLICT (name, version, cve_id) DO UPDATE SET
|
ON CONFLICT (component, name, version, cve_id) DO UPDATE SET
|
||||||
has_vulnerabilities = EXCLUDED.has_vulnerabilities,
|
has_vulnerabilities = EXCLUDED.has_vulnerabilities,
|
||||||
inserted_at = NOW(),
|
inserted_at = NOW(),
|
||||||
description = NULL,
|
description = NULL,
|
||||||
@@ -157,11 +242,10 @@ def insert_vulnerabilities(conn, name, version, vulnerabilities):
|
|||||||
last_modified = NULL;
|
last_modified = NULL;
|
||||||
"""
|
"""
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(query, (name, version))
|
cur.execute(query, (component, name, version))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Подключаемся к БД
|
|
||||||
conn = psycopg2.connect(
|
conn = psycopg2.connect(
|
||||||
host=DB_HOST,
|
host=DB_HOST,
|
||||||
port=DB_PORT,
|
port=DB_PORT,
|
||||||
@@ -170,31 +254,103 @@ def main():
|
|||||||
password=DB_PASSWORD
|
password=DB_PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Jenkins
|
||||||
|
component = "jenkins"
|
||||||
jenkins_version = get_jenkins_version()
|
jenkins_version = get_jenkins_version()
|
||||||
if not jenkins_version:
|
if not jenkins_version:
|
||||||
print("Could not determine Jenkins version.")
|
print("Could not determine Jenkins version.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Обрабатываем Jenkins Core
|
|
||||||
jenkins_cpe = f"cpe:2.3:a:jenkins:jenkins:{jenkins_version}:*:*:*:-:*:*:*"
|
jenkins_cpe = f"cpe:2.3:a:jenkins:jenkins:{jenkins_version}:*:*:*:-:*:*:*"
|
||||||
print(f"Jenkins version: {jenkins_version}")
|
print(f"Jenkins version: {jenkins_version}")
|
||||||
print(f"Jenkins CPE: {jenkins_cpe}")
|
print(f"Jenkins CPE: {jenkins_cpe}")
|
||||||
jenkins_vulns = fetch_cves_by_cpe(jenkins_cpe)
|
jenkins_vulns = fetch_cves_by_cpe(jenkins_cpe)
|
||||||
insert_vulnerabilities(conn, "jenkins_core", jenkins_version, jenkins_vulns)
|
insert_vulnerabilities(conn, component, "jenkins_core", jenkins_version, jenkins_vulns)
|
||||||
|
|
||||||
# Обрабатываем плагины
|
# plugins = get_jenkins_plugins()
|
||||||
plugins = get_jenkins_plugins()
|
# print(f"\nFound {len(plugins)} plugins installed.")
|
||||||
print(f"\nFound {len(plugins)} plugins installed.")
|
# for shortName, version in plugins:
|
||||||
|
# plugin_cpe = f"cpe:2.3:a:jenkins:{shortName}:{version}:*:*:*:*:jenkins:*:*"
|
||||||
|
# print(f"\nPlugin: {shortName}, version: {version}")
|
||||||
|
# print(f"CPE: {plugin_cpe}")
|
||||||
|
# plugin_vulns = fetch_cves_by_cpe(plugin_cpe)
|
||||||
|
|
||||||
for shortName, version in plugins:
|
# plugin_name = f"{shortName}_plugin"
|
||||||
plugin_cpe = f"cpe:2.3:a:jenkins:{shortName}:{version}:*:*:*:*:jenkins:*:*"
|
# # Для плагинов также component = 'jenkins', так как они относятся к Jenkins
|
||||||
print(f"\nPlugin: {shortName}, version: {version}")
|
# insert_vulnerabilities(conn, component, plugin_name, version, plugin_vulns)
|
||||||
print(f"CPE: {plugin_cpe}")
|
|
||||||
plugin_vulns = fetch_cves_by_cpe(plugin_cpe)
|
# Vault
|
||||||
|
vault_version = get_vault_version()
|
||||||
|
if not vault_version:
|
||||||
|
print("Could not determine Vault version.")
|
||||||
|
sys.exit(1)
|
||||||
|
vault_version_clean = vault_version.split('+')[0]
|
||||||
|
vault_cpe = f"cpe:2.3:a:hashicorp:vault:{vault_version_clean}:*:*:*:enterprise:*:*:*"
|
||||||
|
print(f"Vault version: {vault_version}")
|
||||||
|
print(f"Vault CPE: {vault_cpe}")
|
||||||
|
# Для Vault component = 'vault'
|
||||||
|
vault_vulns = fetch_cves_by_cpe(vault_cpe)
|
||||||
|
insert_vulnerabilities(conn, "vault", "vault", vault_version, vault_vulns)
|
||||||
|
|
||||||
|
# Artifactory
|
||||||
|
artifactory_version = get_artifactory_version()
|
||||||
|
if not artifactory_version:
|
||||||
|
print("Could not determine Artifactory version.")
|
||||||
|
sys.exit(1)
|
||||||
|
artifactory_cpe = f"cpe:2.3:a:jfrog:artifactory:{artifactory_version}:*:*:*:*:*:*:*"
|
||||||
|
print(f"Artifactory version: {artifactory_version}")
|
||||||
|
print(f"Artifactory CPE: {artifactory_cpe}")
|
||||||
|
artifactory_vulns = fetch_cves_by_cpe(artifactory_cpe)
|
||||||
|
insert_vulnerabilities(conn, "artifactory", "artifactory", artifactory_version, artifactory_vulns)
|
||||||
|
|
||||||
|
# SonarQube
|
||||||
|
sonarqube_version = get_sonarqube_version()
|
||||||
|
if not sonarqube_version:
|
||||||
|
print("Could not determine SonarQube version.")
|
||||||
|
sys.exit(1)
|
||||||
|
sonarqube_cpe = f"cpe:2.3:a:sonarsource:sonarqube:{sonarqube_version}:*:*:*:*:*:*:*"
|
||||||
|
print(f"SonarQube version: {sonarqube_version}")
|
||||||
|
print(f"SonarQube CPE: {sonarqube_cpe}")
|
||||||
|
sonarqube_vulns = fetch_cves_by_cpe(sonarqube_cpe)
|
||||||
|
insert_vulnerabilities(conn, "sonarqube", "sonarqube", sonarqube_version, sonarqube_vulns)
|
||||||
|
|
||||||
|
# Crowd
|
||||||
|
crowd_version = get_crowd_version()
|
||||||
|
if not crowd_version:
|
||||||
|
print("Could not determine Crowd version.")
|
||||||
|
sys.exit(1)
|
||||||
|
crowd_cpe = f"cpe:2.3:a:atlassian:crowd:{crowd_version}:*:*:*:*:*:*:*"
|
||||||
|
print(f"Crowd version: {crowd_version}")
|
||||||
|
print(f"Crowd CPE: {crowd_cpe}")
|
||||||
|
crowd_vulns = fetch_cves_by_cpe(crowd_cpe)
|
||||||
|
insert_vulnerabilities(conn, "crowd", "crowd", crowd_version, crowd_vulns)
|
||||||
|
|
||||||
|
# GitHub Enterprise
|
||||||
|
ghe_version = get_ghe_version()
|
||||||
|
if not ghe_version:
|
||||||
|
print("Could not determine GitHub Enterprise version.")
|
||||||
|
sys.exit(1)
|
||||||
|
# Предположим такой CPE для GitHub Enterprise.
|
||||||
|
# Если точного CPE нет, можно поискать в NVD. Допустим:
|
||||||
|
# cpe:2.3:a:github:enterprise_server:<version>:*:*:*:*:*:*:*
|
||||||
|
ghe_cpe = f"cpe:2.3:a:github:enterprise_server:{ghe_version}:*:*:*:*:*:*:*"
|
||||||
|
print(f"GitHub Enterprise version: {ghe_version}")
|
||||||
|
print(f"GitHub Enterprise CPE: {ghe_cpe}")
|
||||||
|
ghe_vulns = fetch_cves_by_cpe(ghe_cpe)
|
||||||
|
insert_vulnerabilities(conn, "github_enterprise", "github_enterprise", ghe_version, ghe_vulns)
|
||||||
|
|
||||||
|
# ArgoCD
|
||||||
|
argocd_version = get_argocd_version()
|
||||||
|
if not argocd_version:
|
||||||
|
print("Could not determine ArgoCD version.")
|
||||||
|
sys.exit(1)
|
||||||
|
# Формируем CPE для ArgoCD:
|
||||||
|
argocd_cpe = f"cpe:2.3:a:argoproj:argo-cd:{argocd_version}:*:*:*:*:*:*:*"
|
||||||
|
print(f"ArgoCD version: {argocd_version}")
|
||||||
|
print(f"ArgoCD CPE: {argocd_cpe}")
|
||||||
|
|
||||||
|
argocd_vulns = fetch_cves_by_cpe(argocd_cpe)
|
||||||
|
insert_vulnerabilities(conn, "argocd", "argocd", argocd_version, argocd_vulns)
|
||||||
|
|
||||||
# Добавляем суффикс _plugin для отличия в графиках
|
|
||||||
plugin_name = f"{shortName}_plugin"
|
|
||||||
insert_vulnerabilities(conn, plugin_name, version, plugin_vulns)
|
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user