diff --git a/.gitignore b/.gitignore index cd948f2..d91148f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ plan/ .idea/ Audit_*/ *.csv -*.bat \ No newline at end of file +*.bat +*.log \ No newline at end of file diff --git a/AuditLogCLI.ps1 b/AuditLogCLI.ps1 index aff3d82..9eb64d8 100644 --- a/AuditLogCLI.ps1 +++ b/AuditLogCLI.ps1 @@ -3,11 +3,18 @@ CLI d'export des journaux d'audit pour automates Distech Controls Eclypse. .DESCRIPTION - AuditLogCLI recupere les journaux d'audit (audit-logs) de chaque automate - Distech Controls Eclypse liste dans un CSV d'entree, via leur API REST, - et les enregistre au format CSV (un fichier par automate, ou un fichier + AuditLogCLI recupere les journaux d'audit de chaque automate Distech + Controls Eclypse liste dans un CSV d'entree, via leur API REST, et les + enregistre au format CSV (un fichier par automate, ou un fichier consolide unique). + Detection automatique de la version d'API par automate : + - API v2 (package de diagnostic) essayee en premier. Le zip retourne + est extrait et les evenements connexion/deconnexion (logger + DConnection) des logs eclypse_*.log et karaf.log sont convertis en CSV. + - En cas d'erreur HTTP sur l'appel v2, bascule automatique sur + l'API v1 (audit-logs), qui retourne directement un CSV. + .PARAMETER CsvInput Chemin vers le fichier CSV d'entree (separateur point-virgule). Colonnes obligatoires : Hostname, Current Ip, HttpPort, HttpsPort. @@ -46,9 +53,10 @@ .NOTES Prerequis : PowerShell 5.1+ (inclus dans Windows 10/11) - API : Distech Controls Eclypse REST API v1 - Securite : TLS 1.2, certificats auto-signes acceptes - Important : les noms de query params "startDate"/"endDate" sont une + API : Distech Controls Eclypse REST API v1 (audit-logs) et v2 + (package de diagnostic), detection automatique par automate + Securite : TLS 1.0/1.1/1.2, certificats auto-signes acceptes + Important : les noms de query params "startDate"/"endDate" (v1) sont une hypothese non validee contre la documentation officielle de l'API audit-logs. A verifier avant utilisation en production. #> @@ -76,6 +84,8 @@ $ProgressPreference = 'SilentlyContinue' # UTILITAIRES # ===================================================================== +$script:LogFilePath = Join-Path (Get-Location) "AuditLogCLI_$(Get-Date -Format 'yyyy-MM-dd_HH-mm').log" + function Write-Log { param( [string]$Message, @@ -86,6 +96,7 @@ function Write-Log { $tag = switch ($Level) { "INFO" {"[INFO ]"} "WARN" {"[WARN ]"} "ERROR" {"[ERR ]"} "OK" {"[ OK ]"} } $color = switch ($Level) { "INFO" {"Cyan"} "WARN" {"Yellow"} "ERROR" {"Red"} "OK" {"Green"} } Write-Host "$ts $tag $Message" -ForegroundColor $color + Add-Content -Path $script:LogFilePath -Value "$ts $tag $Message" -Encoding UTF8 } # ===================================================================== @@ -238,6 +249,81 @@ function Invoke-AuditLogGet { } } +# ===================================================================== +# FONCTIONS : API v2 (package de diagnostic) +# ===================================================================== +function Get-DiagnosticPackageUrl { + param([string]$BaseUrl) + return "$BaseUrl/api/rest/v2/services/packages/diagnostic" +} + +function Invoke-DiagnosticGet { + param( + [string]$Url, + [string]$Username, + [AllowEmptyString()][string]$Password + ) + $headers = Get-AuthHeader -Username $Username -Password $Password + $zipPath = Join-Path $env:TEMP "AuditLogCLI_diag_$(Get-Random).zip" + Invoke-WebRequest -Uri $Url -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 120 -OutFile $zipPath + return $zipPath +} + +function Expand-DiagnosticPackage { + param( + [string]$ZipPath, + [string]$DestinationDir + ) + Expand-Archive -Path $ZipPath -DestinationPath $DestinationDir -Force +} + +# ===================================================================== +# FONCTION : Get-ConnectionEvents +# Extrait les evenements connexion/deconnexion (logger DConnection) des +# logs eclypse_*.log et karaf.log d'un package de diagnostic V2 extrait +# ===================================================================== +function Get-ConnectionEvents { + param( + [string]$LogDir, + [string]$StartDate, + [string]$EndDate + ) + $logFiles = @(Get-ChildItem -Path $LogDir -Filter "eclypse_*.log" -ErrorAction SilentlyContinue) + $karafLog = @(Get-ChildItem -Path $LogDir -Filter "karaf.log" -ErrorAction SilentlyContinue) + $logFiles += $karafLog + + $startDt = if ($StartDate) { [DateTime]::ParseExact($StartDate, "yyyy-MM-dd", $null) } else { $null } + $endDt = if ($EndDate) { [DateTime]::ParseExact($EndDate, "yyyy-MM-dd", $null).AddDays(1) } else { $null } + + $events = @() + foreach ($logFile in $logFiles) { + $hits = Select-String -Path $logFile.FullName -Pattern "DConnection.*'(.+?)' (connected|disconnected)" + foreach ($hit in $hits) { + $tsPart = $hit.Line.Split('|')[0].Trim() + $ts = [DateTime]::MinValue + if (-not [DateTime]::TryParseExact($tsPart, "yyyy-MM-ddTHH:mm:ss,fff", $null, [System.Globalization.DateTimeStyles]::None, [ref]$ts)) { + continue + } + if ($startDt -and $ts -lt $startDt) { continue } + if ($endDt -and $ts -ge $endDt) { continue } + + $events += [PSCustomObject]@{ + Timestamp = $ts + User = $hit.Matches[0].Groups[1].Value + Action = $hit.Matches[0].Groups[2].Value + SourceFile = $logFile.Name + } + } + } + return @($events | Sort-Object Timestamp) +} + +function ConvertTo-DiagnosticCsv { + param([array]$Events) + if ($Events.Count -eq 0) { return "" } + return ($Events | ConvertTo-Csv -NoTypeInformation | Out-String) +} + # ===================================================================== # VALIDATION DES PARAMETRES DE DATE # ===================================================================== @@ -290,11 +376,38 @@ foreach ($automate in $automates) { } $creds = Get-Credentials -Automate $automate -DefaultUsername $Username -DefaultPassword $Password - $url = Get-AuditLogUrl -BaseUrl $baseUrl -StartDate $StartDate -EndDate $EndDate - Write-Log -Message "[$hostname] GET $url (user: $($creds.Username))" -Level INFO + $content = $null + $isV2 = $false + $diagUrl = Get-DiagnosticPackageUrl -BaseUrl $baseUrl + $zipPath = $null + try { + Write-Log -Message "[$hostname] GET $diagUrl (V2, user: $($creds.Username))" -Level INFO + $zipPath = Invoke-DiagnosticGet -Url $diagUrl -Username $creds.Username -Password $creds.Password + $isV2 = $true + } + catch { + Write-Log -Message "[$hostname] V2 indisponible ($($_.Exception.Message)), bascule V1" -Level WARN + } - $content = Invoke-AuditLogGet -Url $url -Username $creds.Username -Password $creds.Password + if ($isV2) { + $tempDir = Join-Path $env:TEMP "AuditLogCLI_$($ip)_$(Get-Random)" + try { + Expand-DiagnosticPackage -ZipPath $zipPath -DestinationDir $tempDir + $events = Get-ConnectionEvents -LogDir (Join-Path $tempDir "logs") -StartDate $StartDate -EndDate $EndDate + $content = ConvertTo-DiagnosticCsv -Events $events + Write-Log -Message "[$hostname] $($events.Count) evenement(s) connexion/deconnexion extrait(s) (V2)" -Level OK + } + finally { + Remove-Item -Path $zipPath -Force -ErrorAction SilentlyContinue + Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } + } + else { + $url = Get-AuditLogUrl -BaseUrl $baseUrl -StartDate $StartDate -EndDate $EndDate + Write-Log -Message "[$hostname] GET $url (V1, user: $($creds.Username))" -Level INFO + $content = Invoke-AuditLogGet -Url $url -Username $creds.Username -Password $creds.Password + } if ($OutputFormat -eq "PerAutomate") { $filePath = Join-Path $outputDir "$ip.csv" diff --git a/README.md b/README.md index 77d1cfa..c02b1be 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,15 @@ Se connecte à chaque automate listé dans un CSV, récupère son journal d'audi Un filtrage optionnel par période (`-StartDate`/`-EndDate`) peut être appliqué à la requête. +### Détection automatique API v1 / v2 + +Le script détecte automatiquement la version d'API supportée par chaque automate, sans configuration : + +1. **API v2** (package de diagnostic) est essayée en premier : `GET /api/rest/v2/services/packages/diagnostic`. La réponse est un fichier `.zip` de diagnostic complet, que le script télécharge, extrait, puis analyse pour en extraire les événements de connexion/déconnexion (`connected`/`disconnected`) présents dans `logs/eclypse_*.log` et `logs/karaf.log`. Le fichier zip et les fichiers extraits sont supprimés automatiquement après traitement ; seul le CSV final est conservé. +2. En cas d'erreur HTTP sur l'appel v2 (404, 401, 500...), le script bascule automatiquement sur **l'API v1** (`GET /api/rest/v1/system/audit-logs`), qui retourne directement un CSV. + +Le CSV obtenu (v1 ou v2) suit ensuite le même traitement de sortie (`PerAutomate` ou `Consolidated`). + --- ## Utilisation avec cadMasterCLI @@ -216,6 +225,10 @@ Vérifier que les colonnes `HttpPort` et `HttpsPort` du CSV contiennent des vale Les noms des paramètres de requête (`startDate`/`endDate`) utilisés par ce script sont une hypothèse non validée contre la documentation officielle de l'API `audit-logs`. Si le filtrage ne fonctionne pas, vérifier la documentation de l'API Eclypse de l'automate concerné. +#### "V2 indisponible, bascule V1" dans les logs + +Message normal (niveau `WARN`) : l'automate ne supporte pas l'API v2 (package de diagnostic), le script utilise donc l'API v1 (`audit-logs`) à la place. Aucune action requise. + # AuditLogCLI @@ -247,6 +260,15 @@ Connects to each controller listed in a CSV, retrieves its audit log via the RES An optional date-range filter (`-StartDate`/`-EndDate`) can be applied to the request. +### Automatic API v1 / v2 detection + +The script automatically detects which API version each controller supports, with no configuration needed: + +1. **API v2** (diagnostic package) is tried first: `GET /api/rest/v2/services/packages/diagnostic`. The response is a full diagnostic `.zip` file, which the script downloads, extracts, then parses to pull out connection/disconnection events (`connected`/`disconnected`) found in `logs/eclypse_*.log` and `logs/karaf.log`. The zip file and extracted files are automatically deleted after processing; only the final CSV is kept. +2. On any HTTP error from the v2 call (404, 401, 500...), the script automatically falls back to **API v1** (`GET /api/rest/v1/system/audit-logs`), which returns a CSV directly. + +The resulting CSV (v1 or v2) then goes through the same output handling (`PerAutomate` or `Consolidated`). + --- ## Using with cadMasterCLI @@ -435,3 +457,7 @@ Check that the CSV's `HttpPort` and `HttpsPort` columns contain valid values. A #### The `-StartDate`/`-EndDate` filter seems to have no effect The query parameter names (`startDate`/`endDate`) used by this script are an unverified assumption against the official `audit-logs` API documentation. If filtering does not work, check the Eclypse API documentation for the specific controller. + +#### "V2 indisponible, bascule V1" in the logs + +Normal message (`WARN` level): the controller does not support the v2 API (diagnostic package), so the script uses the v1 API (`audit-logs`) instead. No action needed.