Utilisation d’OpenSSL 3 avec Apache 2.4

Par défaut lorsqu’on installe Apache depuis les dépôts de sa distribution, Apache utilise une ancienne version d’OpenSSL. A l’heure où des vulnérabilités ont été rendues publiques sur certaines versions d’OpenSSL, je vais détailler dans cet article comment compiler OpenSSL 3.0.3 et Apache 2.4.53 afin qu’il utilise les bonnes librairies sur un système CentOS 7.

Les paquets openssl étant installés nativement sur une majorité d’images de CentOS, il est nécessaire de les retirer.

sudo yum remove openssl

Mettre à jour le système et installer les dépendances nécessaires.

sudo yum update
sudo yum install wget gcc make perl perl-IPC-Cmd perl-Test-Simple
sudo yum install expat-devel pcre-devel apr-devel apr-util-devel

Télécharger et extraire les sources d’OpenSSL. Il sera peut-être nécessaire d’adapter le lien, sachant qu’à l’heure actuelle, la dernière version disponible d’OpenSSL est la 3.0.3.

cd /usr/src
sudo wget https://www.openssl.org/source/openssl-3.0.3.tar.gz
sudo tar -xvzf openssl-3.0.3.tar.gz

Compiler les sources.

cd openssl-3.0.3
sudo chmod +x ./config
sudo ./config
sudo make
sudo make test
sudo make install

Créer les liens symboliques pour pouvoir utiliser openssl.

sudo ln -s /usr/local/lib64/libssl.so.3 /usr/lib64/libssl.so.3
sudo ln -s /usr/local/lib64/libcrypto.so.3 /usr/lib64/libcrypto.so.3

Appeler openssl pour vérifier qu’il est bien installé. La commande doit nous renvoyer le numéro de version et sa date de publication.

openssl version

Maintenant qu’OpenSSL est bien installé, il faut télécharger les sources d’Apache 2.4. Ici aussi, il pourra être nécessaire d’adapter le lien ; la dernière version disponible à date de cet article est la 2.4.53.

cd /usr/src/
sudo wget https://dlcdn.apache.org/httpd/httpd-2.4.53.tar.gz
sudo tar -xzf httpd-2.4.53.tar.gz
cd httpd-2.4.53

C’est ici que nous allons configurer la compilation d’Apache afin que le daemon httpd utilise notre version d’OpenSSL et non le module en version 1 généralement intégré par défaut.

sudo ./configure --with-ssl=/usr/local/ssl --enable-ssl --enable-so
sudo make
sudo make install

Éditer le fichier /usr/local/apache2/conf/httpd.conf avec les privilèges de superutilisateur et décommenter la ligne LoadModule ssl_module modules/mod_ssl.so.

Le module doit apparaître chargé :

Démarrer le daemon httpd :

sudo /usr/local/apache2/bin/apachectl start

Pour vérifier la version d’OpenSSL utilisée par le serveur web, il suffit de l’interroger sur la boucle locale :

curl --head http://localhost/

Mes premières Azure Policy

Ayant travaillé sur Microsoft Azure récemment et dans un besoin de gouvernance, j’ai mis en place quelques policy sur l’environnement Azure sur lequel j’opère actuellement. Je vais présenter dans cet article quelques policy permettant de contrôler divers éléments.

Cette première policy a été importée depuis GitHub. Elle permet de surveiller la présence d’un lock sur un Resource Group si celui-ci possède un tag renseigné à une valeur spécifique. Par exemple, on peut placer un tag ProtectedByLock à True pour indiquer à la policy que ce Resource Group doit être protégé par un lock. Si jamais le tag n’est pas présent ou que celui-ci n’est pas à True, alors la policy ne remontera rien si ce Resource Group n’est pas protégé par un lock.

{
    "mode": "All",
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Resources/subscriptions/resourceGroups"
          },
          {
            "field": "[concat('tags[', parameters('tagName'), ']')]",
            "equals": "[parameters('tagValue')]"
          }
        ]
      },
      "then": {
        "effect": "auditIfNotExists",
        "details": {
          "type": "Microsoft.Authorization/locks",
          "existenceCondition": {
            "field": "Microsoft.Authorization/locks/level",
            "equals": "CanNotDelete"
          }
        }
      }
    },
    "parameters": {
      "tagName": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Name",
          "description": "The tag name to audit against (i.e. Environment, CostCenter, etc.)"
        }
      },
      "tagValue": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Value",
          "description": "Value of the tag to audit against (i.e. Prod/UAT/TEST, 12345, etc.)"
        }
      }
    }
  }

Cette policy permet de vérifier que les disques virtuels existants sont tous bien rattachés à une machine virtuelle.

{
    "mode": "All",
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Compute/Disks"
          },
          {
            "anyOf": [
              {
                "not": {
                  "field": "Microsoft.Compute/Disks/managedBy",
                  "exists": "True"
                }
              }     
            ]
          }
        ]
      },
      "then": {
        "effect": "audit"
      }
    },
    "parameters": {}
  }

De manière similaire, cette policy permet de mettre en évidence la présence de NIC orphelines.

{
  "mode": "All",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Network/networkInterfaces"
        },
        {
          "anyOf": [
            {
              "not": {
                "field": "Microsoft.Network/networkInterfaces/VirtualMachine",
                "exists": "True"
              }
            }
          ]
        }
      ]
    },
    "then": {
      "effect": "audit"
    }
  },
  "parameters": {}
}

Cette dernière policy permet de vérifier que les machines n’ayant pas un tag Backup à No sont bien sauvegardées par Azure Backup. Ici aussi, il est possible d’adapter le contrôle en fonction du tag et de la valeur attendue pour mettre en alerte les irrégularités.

{
    "mode": "Indexed",
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Compute/virtualMachines"
          },
          {
            "anyOf": [
              {
                "not": {
                  "field": "[concat('tags[', parameters('TagName'), ']')]",
                  "equals": "[parameters('TagValue')]"
                }
              }
            ]
          }
        ]
      },
      "then": {
        "effect": "[parameters('effect')]",
        "details": {
          "type": "Microsoft.RecoveryServices/backupprotecteditems"
        }
      }
    },
    "parameters": {
      "effect": {
        "type": "String",
        "metadata": {
          "displayName": "Effect",
          "description": "Enable or disable the execution of the policy"
        },
        "allowedValues": [
          "AuditIfNotExists",
          "Disabled"
        ],
        "defaultValue": "AuditIfNotExists"
      },
      "TagValue": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Value",
          "description": "Tag Value (No)"
        },
        "allowedValues": [
          "No"
        ],
        "defaultValue": "No"
      },
      "TagName": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Name",
          "description": "Tag Name (Backup)"
        },
        "allowedValues": [
          "Backup"
        ],
        "defaultValue": "Backup"
      }
    }
  }

Powershell : expiration des secrets et certificats des applications Azure ActiveDirectory

Ce script permet d’afficher la liste des App Registrations Azure ActiveDirectory ayant un ou des secrets ou certificats expirant à 90 jours, 30 jours ou déjà expirés.

Il est bien évidemment possible d’adapter le script pour le faire tourner dans un runbook Azure ou sur un serveur de manière non-interactive en adaptant la partie authentification.

Connect-AzureAD
$AADAppsColl = Get-AzureADApplication -All:$true
foreach($AADApps in $AADAppsColl) {
    $AppID = $AADApps.AppID
    $AADApp = Get-AzureADApplication -Filter "AppID eq '$AppID'"
    $PassCreds = $AADApp.PasswordCredentials
    if ($null -ne $PassCreds) {
        foreach($PassCred in $PassCreds) {
            if($PassCred.EndDate -gt (Get-Date).AddDays(30) -and $PassCred.EndDate -le (Get-Date).AddDays(90)){
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expires: $($PassCred.EndDate)" -ForegroundColor Green
                Write-Host `r
            }
            if($PassCred.EndDate -gt (Get-Date) -and $PassCred.EndDate -le (Get-Date).AddDays(30)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expires: $($PassCred.EndDate)" -ForegroundColor Orange
                Write-Host `r
            }
            if($PassCred.EndDate -le (Get-Date)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expired: $($PassCred.EndDate)" -ForegroundColor Red
                Write-Host `r
            }
        }
    }
    $KeyCreds = $AADApp.KeyCredentials
    if ($null -ne $KeyCreds) {
        foreach($KeyCred in $KeyCreds) {
            if($KeyCred.EndDate -gt (Get-Date).AddDays(30) -and $KeyCred.EndDate -le (Get-Date).AddDays(90)){
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expires: $($KeyCred.EndDate)" -ForegroundColor Green
                Write-Host `r
            }
            if($KeyCred.EndDate -gt (Get-Date) -and $KeyCred.EndDate -le (Get-Date).AddDays(30)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expires: $($KeyCred.EndDate)" -ForegroundColor Orange
                Write-Host `r
            }
            if($KeyCred.EndDate -le (Get-Date)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expired: $($KeyCred.EndDate)" -ForegroundColor Red
                Write-Host `r
            }
        }
    }
}

Une version commentée du script est disponible en téléchargement.

Powershell : Robocopy Log Parser

Cela faisait quelques temps que j’avais cette idée dans mon backlog cérébral, mais je m’y suis vraiment penché aujourd’hui car il a fallu que je contrôle un gros nombre de fichiers de log générés par une instruction Robocopy (350+).

Le principal challenge pour ce script est d’arriver à gérer correctement les lignes de statistiques de Robocopy puisque l’export est au simple format texte :

------------------------------------------------------------------------------

               Total    Copied   Skipped  Mismatch    FAILED    Extras
    Dirs :      3765      3765         0         0         0         0
   Files :    243503    243503         0         0         0         0
   Bytes :  19.238 g  19.238 g         0         0         0         0
   Times :  44:22:58   6:23:45                       0:00:00   0:26:35
   Ended : Monday, February 30, 1998 3:25:57 AM

Ces lignes ne sont pas directement interprétables comme telles par Powershell, il faut donc jouer du regex pour arriver à obtenir les valeurs qui nous intéressent. Si au départ je voulais simplement ressortir les FAILED, l’effort supplémentaire pour sortir l’intégralité des valeurs pour les répertoires et les fichiers est minime, ce script va donc inclure dans le fichier CSV de sortie l’intégralité des statistiques pour les répertoires et fichiers.

function parse {
    param($inputstr)
    $newstr = $inputstr -replace "[^0-9]" , '-'
    $newstr -match '[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)'
    $stats = @($matches[1],$matches[2],$matches[3],$matches[4],$matches[5],$matches[6])
    return $stats
}

$output=@()
$logfiles = Get-ChildItem "*.log" | Select-Object Name
foreach($logfile in $logfiles) {
    Write-Host "Parsing $($logfile.Name)"
    $logcontent = Get-Content $logfile.Name
    $strf = $logcontent[$logcontent.Count-5]
    $strd = $logcontent[$logcontent.Count-6]
    $statf = parse($strf)
    $statd = parse($strd)
    $dump = New-Object PSCustomObject
    $dump | Add-Member -Name "Filename" -Value $($logfile.Name) -MemberType NoteProperty
    $dump | Add-Member -Name "Files Total" -Value $statf[1] -MemberType NoteProperty
    $dump | Add-Member -Name "Files Copied" -Value $statf[2] -MemberType NoteProperty
    $dump | Add-Member -Name "Files Skipped" -Value $statf[3] -MemberType NoteProperty
    $dump | Add-Member -Name "Files Mismatched" -Value $statf[4] -MemberType NoteProperty
    $dump | Add-Member -Name "Files FAILED" -Value $statf[5] -MemberType NoteProperty
    $dump | Add-Member -Name "Files Extras" -Value $statf[6] -MemberType NoteProperty
    $dump | Add-Member -Name "Dirs Total" -Value $statd[1] -MemberType NoteProperty
    $dump | Add-Member -Name "Dirs Copied" -Value $statd[2] -MemberType NoteProperty
    $dump | Add-Member -Name "Dirs Skipped" -Value $statd[3] -MemberType NoteProperty
    $dump | Add-Member -Name "Dirs Mismatched" -Value $statd[4] -MemberType NoteProperty
    $dump | Add-Member -Name "Dirs FAILED" -Value $statd[5] -MemberType NoteProperty
    $dump | Add-Member -Name "Dirs Extras" -Value $statd[6] -MemberType NoteProperty
    $output+=$dump
}
$output | Export-Csv robocopy_logs_stats.csv -Delimiter ";" -Encoding utf8

Le script récupère la liste des fichiers en .log dans le répertoire d’exécution pour ensuite les lire. Par chance, les logs Robocopy ont toujours la même structure, ce qui permet d’avoir des valeurs fixes pour séparer les colonnes et récupérer les valeurs qui nous intéressent.

Ensuite, le fichier CSV est facilement interprété par Excel et on peut voir en un clin d’oeil les éventuelles erreurs de transfert et regarder dans le log concerné quels fichiers ou répertoires sont en échec.

Une version commentée du script est disponible en téléchargement.