From efec6f59a35b3879f9e99a29d07a157e83510d01 Mon Sep 17 00:00:00 2001 From: Ruslan Piatrovich Date: Thu, 19 Dec 2024 17:36:30 +0300 Subject: [PATCH] new features --- software_vulnarabilities/jenkins.py | 232 +++++++++++++++++++++++----- 1 file changed, 194 insertions(+), 38 deletions(-) diff --git a/software_vulnarabilities/jenkins.py b/software_vulnarabilities/jenkins.py index 44015d0..6b4d8b3 100644 --- a/software_vulnarabilities/jenkins.py +++ b/software_vulnarabilities/jenkins.py @@ -2,6 +2,8 @@ import requests import sys import time import os +import requests +import re import psycopg2 from psycopg2.extras import execute_values @@ -9,6 +11,18 @@ JENKINS_URL = "https://jenkins.lmru.tech" AUTH_USER = "lm-sa-devsecops" 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_KEY = "aa054ea4-4a99-4f16-b5ed-1f77b5193d80" @@ -44,6 +58,88 @@ def get_jenkins_plugins(): plugin_list.append((shortName, version)) 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']*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): """ Запросить уязвимости из NVD по cpeName. @@ -84,30 +180,20 @@ def fetch_cves_by_cpe(cpe_name, max_retries=3, delay=5): print("All attempts failed unexpectedly. Exiting.") sys.exit(1) -def insert_vulnerabilities(conn, name, version, vulnerabilities): +def insert_vulnerabilities(conn, component, name, version, vulnerabilities): """ Вставить или обновить информацию об уязвимостях в PostgreSQL. - - Если нет уязвимостей: - - вставляем/обновляем запись с cve_id='NO_VULN'. - - Если уязвимости появились: - - удаляем запись 'NO_VULN' (если была), - - вставляем все уязвимости с UPSERT. - - Используем ON CONFLICT DO UPDATE для актуализации данных. + component - строка, например 'jenkins' или 'vault'. """ if vulnerabilities: - # Удаляем запись NO_VULN, если есть delete_query = """ 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: - cur.execute(delete_query, (name, version)) + cur.execute(delete_query, (component, name, version)) conn.commit() - # Вставляем реальные уязвимости records = [] for v in vulnerabilities: cve = v.get("cve", {}) @@ -126,12 +212,13 @@ def insert_vulnerabilities(conn, name, version, vulnerabilities): cvss_data = m.get("cvssData", {}) 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 = """ - 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 - ON CONFLICT (name, version, cve_id) DO UPDATE SET + ON CONFLICT (component, name, version, cve_id) DO UPDATE SET description = EXCLUDED.description, severity = EXCLUDED.severity, published_date = EXCLUDED.published_date, @@ -142,13 +229,11 @@ def insert_vulnerabilities(conn, name, version, vulnerabilities): with conn.cursor() as cur: execute_values(cur, query, records) conn.commit() - else: - # Нет уязвимостей - вставляем/обновляем NO_VULN query = """ - INSERT INTO software_vulnerabilities (name, version, cve_id, has_vulnerabilities) - VALUES (%s, %s, 'NO_VULN', FALSE) - ON CONFLICT (name, version, cve_id) DO UPDATE SET + INSERT INTO software_vulnerabilities (component, name, version, cve_id, has_vulnerabilities) + VALUES (%s, %s, %s, 'NO_VULN', FALSE) + ON CONFLICT (component, name, version, cve_id) DO UPDATE SET has_vulnerabilities = EXCLUDED.has_vulnerabilities, inserted_at = NOW(), description = NULL, @@ -157,11 +242,10 @@ def insert_vulnerabilities(conn, name, version, vulnerabilities): last_modified = NULL; """ with conn.cursor() as cur: - cur.execute(query, (name, version)) + cur.execute(query, (component, name, version)) conn.commit() def main(): - # Подключаемся к БД conn = psycopg2.connect( host=DB_HOST, port=DB_PORT, @@ -170,31 +254,103 @@ def main(): password=DB_PASSWORD ) + # Jenkins + component = "jenkins" jenkins_version = get_jenkins_version() if not jenkins_version: print("Could not determine Jenkins version.") sys.exit(1) - - # Обрабатываем Jenkins Core jenkins_cpe = f"cpe:2.3:a:jenkins:jenkins:{jenkins_version}:*:*:*:-:*:*:*" print(f"Jenkins version: {jenkins_version}") print(f"Jenkins 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() - print(f"\nFound {len(plugins)} plugins installed.") + # plugins = get_jenkins_plugins() + # 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_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) + # plugin_name = f"{shortName}_plugin" + # # Для плагинов также component = 'jenkins', так как они относятся к Jenkins + # insert_vulnerabilities(conn, component, plugin_name, version, plugin_vulns) + + # 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::*:*:*:*:*:*:* + 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()