Compare commits

..

12 Commits

11 changed files with 154 additions and 77 deletions

View File

@ -3,7 +3,7 @@
## Explication de l'usage des scripts et de la configuration : ## Explication de l'usage des scripts et de la configuration :
### Fichier de configuration global settings.json ### Fichier de configuration global settings.json
Les scripts prennent les informations de configuration du fichier de configuration présent dans le répertoire conf, si celui-ci n'existe pas au lancement d'un script, il est automatiquement créé avec des valeurs d'exemples. Un chemin personnalisé vers un fichier de configuration peut être renseigné avec le paramètre -c. Les scripts prennent les informations de configuration du fichier de configuration présent dans le répertoire conf, si celui-ci n'existe pas au lancement d'un script, il est automatiquement créé avec des valeurs d'exemples.
Voici un exemple du fichier de configuration : Voici un exemple du fichier de configuration :
@ -85,6 +85,10 @@ Il récupère le nom de l'utilisateur de staging dans le fichier de configuratio
- **-s / --silent** : exécute le script sans faire de retour dans la console, les informations seront toujours présentes dans les fichiers de log - **-s / --silent** : exécute le script sans faire de retour dans la console, les informations seront toujours présentes dans les fichiers de log
- **-v / --verbose** : affiche des informations lors de son exécution, utile pour résoudre des problèmes liés à des droits d'accès API ou des problèmes d'ouvertures réseaux - **-v / --verbose** : affiche des informations lors de son exécution, utile pour résoudre des problèmes liés à des droits d'accès API ou des problèmes d'ouvertures réseaux
## Installation avec docker
## Dépendances ## Dépendances
Installation des paquets linux : Installation des paquets linux :
@ -98,9 +102,28 @@ apt-get install -y python3 python3-pip git
yum install -y python3 python3-pip git yum install -y python3 python3-pip git
``` ```
**Dépendances python** ## Modules python nécessaires
Voici la liste des modules python nécessaires au fonctionnement des scripts :
- cryptography
- requests
- toml
Un fichier requirements.txt contenant les modules dont dépendent les scripts est présent dans le répertoire scripts.
Les modules peuvent être installés de plusieurs façons :
**Avec le gestionnaire de paquet python Pip**
``` ```
python3 -m pip install cryptography requests toml python -m pip install -r requirements.txt
```
**Avec le gestionnaire de paquet apt (debian/ubuntu)**
```
apt-get install -y python3-cryptography python3-requests python3-toml
```
**Avec le gestionnaire de paquet yum (redhat/centos)**
```
yum install -y python3-cryptography python3-requests python3-toml
``` ```
## Installation des scripts ## Installation des scripts
@ -172,7 +195,7 @@ Description=Script qui assigne les appareils en staging aux utilisateurs en se b
[Timer] [Timer]
OnUnitInactiveSec=1m OnUnitInactiveSec=1m
AccuracySec=1us AccuracySec=1us
Unit=airwatchSync.service Unit=airwatchStaging.service
[Install] [Install]
WantedBy=timers.target WantedBy=timers.target

28
compose.yml Normal file
View File

@ -0,0 +1,28 @@
services:
sync:
image: emitlinks/airwatchConnector
restart: unless-stopped
volumes:
- ./conf:/airwatchConnector/conf
- ./certs:/airwatchConnector/certs
- ./logs:/airwatchConnector/logs
healthcheck:
test: if [ -f $(ls /airwatchConnector/*_SyncGLPI.lock) ]; then exit 0; else exit 1; fi
interval: 5m
start_period: 3h
environment:
TASK: "syncGLPI"
stagingAssignment:
image: emitlinks/airwatchConnector
restart: unless-stopped
volumes:
- ./conf:/airwatchConnector/conf
- ./certs:/airwatchConnector/certs
- ./logs:/airwatchConnector/logs
healthcheck:
test: if [ -f $(ls /airwatchConnector/*_StagingUserAssignation.lock) ]; then exit 0; else exit 1; fi
interval: 15s
start_period: 5m
environment:
TASK: "stagingAssignment"

10
dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python
RUN <<-EOF
apt-get update
apt-get install -y python3-cryptography python3-requests python3-toml
EOF
ADD scripts\* /airwatchConnector
ADD pre-start.py /
ADD start.sh /
RUN chmod u+x /start.sh
ENTRYPOINT ["/start.sh"]

26
pre-start.py Normal file
View File

@ -0,0 +1,26 @@
import os
import toml
baseDir = "/airwatchConnector"
logDir = f"{baseDir}/logs"
confDir = f"{baseDir}/conf"
confFiles = os.listdir(confDir)
# On récupère que les fichiers .conf
confFiles = [conf for conf in confFiles if os.path.isfile(f"{confDir}/{conf}") and not conf.endswith(".conf")]
for conf in confFiles:
# On forme un nom à partir du nom du fichier de conf sans l'extension
# et on enlève les espaces
confName = conf[:5].replace(' ', '')
with open(f"{confDir}/{conf}", "r") as f:
settings = toml.load(f)
# Create log folder
if(settings["LOGS"]["Enabled"]):
logPath = settings["LOGS"].get(Path)
if not os.path.exists(f"{logDir}/{logPath}"):
os.makedirs(f"{logDir}/{logPath}")

View File

@ -2,7 +2,6 @@
import os import os
import argparse import argparse
import logging import logging
import time
from functions import getSettings from functions import getSettings
from includes.airwatchAPI import * from includes.airwatchAPI import *
from includes.GLPIAPI import * from includes.GLPIAPI import *
@ -21,7 +20,7 @@ args = parser.parse_args()
if(args.configpath != None and args.configpath != ''): if(args.configpath != None and args.configpath != ''):
settings = getSettings(args.configpath) settings = getSettings(args.configpath)
else: else:
settings = getSettings("./conf/settings.conf") settings = getSettings(f"{os.path.realpath(os.path.dirname(__file__))}/conf/settings.conf")
#=========== Configuration des logs ===========# #=========== Configuration des logs ===========#
@ -42,7 +41,7 @@ if(settings["LOGS"]["Enabled"]):
if(settings["LOGS"].get("Path") and settings["LOGS"].get("Path") != ""): if(settings["LOGS"].get("Path") and settings["LOGS"].get("Path") != ""):
fileHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}stagingUserAssignation.log") fileHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}stagingUserAssignation.log")
else: else:
fileHandler = logging.FileHandler('./logs/stagingUserAssignation.log') fileHandler = logging.FileHandler(f'{os.path.realpath(os.path.dirname(__file__))}/logs/stagingUserAssignation.log')
fileHandler.setLevel(logginglevel) fileHandler.setLevel(logginglevel)
fileHandler.setFormatter(formatter) fileHandler.setFormatter(formatter)
logger.addHandler(fileHandler) logger.addHandler(fileHandler)
@ -57,7 +56,8 @@ if(not args.silent):
#======== Paramètres du script ========# #======== Paramètres du script ========#
# Emplacement du verrou # Emplacement du verrou
lockFile = './airwatchStagingUserAssignation.lock' nameForLockFile = settings["GLPI"]["UserAgent"].replace(' ', '-')
lockFile = f'{os.path.realpath(os.path.dirname(__file__))}/{nameForLockFile}_StagingUserAssignation.lock'
debug=args.debug debug=args.debug
@ -67,21 +67,13 @@ if(args.staginguser != None):
stagingUser = args.staginguser stagingUser = args.staginguser
# ====================================== # # ====================================== #
# Vérification de la présence du verrou avant de continuer # Vérification de la présence du verrou avant de continuer
if(os.path.isfile(lockFile) and not args.force): if(os.path.isfile(lockFile) and not args.force):
# Récupération du temps de création de verrou en minutes logger.debug('Lock file exists, exiting...')
lockTime = (time.time() - os.path.getmtime(lockFile)) // 60 exit(0)
# Recréation du verrou s'il existe depuis plus de 3 heures (crash)
# sinon on quitte, une synchro est déjà en cours
if(lockTime > 180):
os.remove(lockFile)
open(lockFile, "w").close()
else:
logger.debug('Lock file exists, exiting...')
exit(0)
else: else:
# Création du verrou s'il n'existe pas
open(lockFile, "w").close() open(lockFile, "w").close()
# Initialisation de l'api Airwatch # Initialisation de l'api Airwatch
@ -151,9 +143,15 @@ for device in devices:
else: else:
logger.warning(f"Device with id {device.Id} is not assigned to any user in GLPI, skipping the device") logger.warning(f"Device with id {device.Id} is not assigned to any user in GLPI, skipping the device")
elif(deviceCount > 1): elif(deviceCount > 1):
logger.info(f"More than one entry found in GLPI for device with id {device.Id}") logger.error(f"{count} devices matching airwatch device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI trashbin (GLPI ids = {', '.join(deviceID)}), skipping this device...")
else: else:
logger.error(f"Device {device.Id} with serialnumber {device.SerialNumber} not found in GLPI (in trash bin ?)") deviceIDTrash, dataTrash, deviceCountTrash = glpiapi.GetDevice(device, trashbin=True)
if(countTrash > 1):
logger.error(f"{countTrash} devices matching airwatch device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI trashbin (GLPI ids = {', '.join(deviceIDTrash)}), skipping this device...")
elif(countTrash == 1):
logger.warning(f"Device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI trashbin (GLPI id={deviceIDTrash}), skipping...")
else:
logger.error(f"Device {device.FriendlyName} (Airwatch id={device.Id}) not found in GLPI.")
# Suppression du verrou # Suppression du verrou

View File

@ -5,7 +5,6 @@ def getSettings(settingsPath):
settingsDefault =""" settingsDefault ="""
[AIRWATCH] [AIRWATCH]
Server = "https://airwatchServer" Server = "https://airwatchServer"
ConsoleURI = "https://airwatchConsole"
APIKey = "APIKEY" APIKey = "APIKEY"
# Méthode d'authentification (CMSURL or PASSWORD) # Méthode d'authentification (CMSURL or PASSWORD)

View File

@ -110,23 +110,12 @@ class GLPIAPI:
} }
uri = f"{self.Server}/apirest.php/Computer/" uri = f"{self.Server}/apirest.php/Computer/"
return requests.put(uri, headers=self.Headers, json=body) return requests.put(uri, headers=self.Headers, json=body)
def UpdateAirwatchLink(self, deviceid, airwatchlink):
body = {
"input": {
"id": deviceid,
"appareilsurmagentafield": airwatchlink
}
}
uri = f"{self.Server}/apirest.php/Computer/"
return requests.put(uri, headers=self.Headers, json=body)
def CreateInventoryForAirwatchDevice(self, device, deviceName, apps=None): def CreateInventoryForAirwatchDevice(self, device, deviceName, apps=None):
platforms = { platforms = {
2:"Apple iOS", 2:"Apple iOS",
5:"Android", 5:"Android",
12:"Windows" 12:"Windows Desktop"
} }
if(device.PlatformId in platforms.keys()): if(device.PlatformId in platforms.keys()):
@ -151,28 +140,10 @@ class GLPIAPI:
osArch = "Unknown" osArch = "Unknown"
softwareArch = "Unknown" softwareArch = "Unknown"
windowsOSTranslation = {
"10.0.19043":"10 21H1",
"10.0.19044":"10 21H2",
"10.0.19045":"10 22H2",
"10.0.22000":"11 21H2",
"10.0.22621":"11 22H2",
"10.0.22631":"11 23H2",
"10.0.26100":"11 24H2",
"10.0.26200":"11 25H2"
}
logDate = datetime.strptime(device.LastSeen, "%Y-%m-%dT%H:%M:%S.%f").strftime("%Y-%m-%d %H:%M:%S") logDate = datetime.strptime(device.LastSeen, "%Y-%m-%dT%H:%M:%S.%f").strftime("%Y-%m-%d %H:%M:%S")
inventory = GLPIInventory(logdate=logDate, versionclient=self.UserAgent, tag=device.Group, deviceid=f"{deviceName} - {device.SerialNumber}", itemtype="Computer") inventory = GLPIInventory(logdate=logDate, versionclient=self.UserAgent, tag=device.Group, deviceid=f"{deviceName} - {device.SerialNumber}", itemtype="Computer")
if(platformName == "Windows"): inventory.SetOperatingSystem(platformName, device.OS, osArch)
if(device.OS in windowsOSTranslation.keys()):
inventory.SetOperatingSystem(platformName, windowsOSTranslation[str(device.OS)], osArch)
else:
platformName = "Windows Desktop"
inventory.SetOperatingSystem(platformName, device.OS, osArch)
else:
inventory.SetOperatingSystem(platformName, device.OS, osArch)
inventory.SetHardware(deviceName, device.Uuid, device.TotalMemory) inventory.SetHardware(deviceName, device.Uuid, device.TotalMemory)
inventory.AddUser(device.User) inventory.AddUser(device.User)

View File

@ -2,7 +2,6 @@
import os import os
import argparse import argparse
import logging import logging
import time
from datetime import datetime from datetime import datetime
from functions import getSettings from functions import getSettings
from includes.airwatchAPI import * from includes.airwatchAPI import *
@ -23,7 +22,7 @@ args = parser.parse_args()
if(args.configpath != None and args.configpath != ''): if(args.configpath != None and args.configpath != ''):
settings = getSettings(args.configpath) settings = getSettings(args.configpath)
else: else:
settings = getSettings(f"{os.path.realpath(os.path.dirname(__file__))}/conf/settings.conf") settings = getSettings("{os.path.realpath(os.path.dirname(__file__))}/conf/settings.conf")
#=========== Configuration des logs ===========# #=========== Configuration des logs ===========#
@ -106,21 +105,11 @@ platformFilterOut = [12]
# ====================================== # # ====================================== #
# Vérification de la présence du verrou avant de continuer # Vérification de la présence du verrou avant de continuer
if(os.path.isfile(lockFile) and not args.force): if(os.path.isfile(lockFile) and not args.force):
# Récupération du temps de création de verrou en minutes logger.debug('Lock file exists, exiting...')
lockTime = (time.time() - os.path.getmtime(lockFile)) // 60 exit(0)
# Recréation du verrou s'il existe depuis plus de 3 heures (crash)
# sinon on quitte, une synchro est déjà en cours
if(lockTime > 180):
os.remove(lockFile)
open(lockFile, "w").close()
else:
logger.debug('Lock file exists, exiting...')
exit(0)
else: else:
# Création du verrou s'il n'existe pas
open(lockFile, "w").close() open(lockFile, "w").close()
logger.info("========= Synchronization started =========") logger.info("========= Synchronization started =========")
@ -227,17 +216,11 @@ for device in devices:
inventory = glpiapi.CreateInventoryForAirwatchDevice(device, data["1"], apps) inventory = glpiapi.CreateInventoryForAirwatchDevice(device, data["1"], apps)
# Mise à jour du friendly name sur Airwatch # Mise à jour du friendly name sur Airwatch
platformName = inventory.operatingsystem["name"] platformName = inventory.operatingsystem["name"]
osVersion = inventory.operatingsystem["version"] if(device.FriendlyName != f"{data['1']} {platformName} {device.OS} - {device.User}"):
if(device.FriendlyName != f"{data['1']} {platformName} {osVersion} - {device.User}"): newFriendlyName = f"{data['1']} {platformName} {device.OS} - {device.User}"
newFriendlyName = f"{data['1']} {platformName} {osVersion} - {device.User}"
logger.info(f"Updating device friendlyname to {newFriendlyName}") logger.info(f"Updating device friendlyname to {newFriendlyName}")
airwatch.SetDeviceFriendlyName(device, newFriendlyName) airwatch.SetDeviceFriendlyName(device, newFriendlyName)
# Mise à jour de l'url vers la page airwatch de l'appareil sur GLPI
airwatchlink = f"{settings['AIRWATCH']['ConsoleURI']}/AirWatch/#/AirWatch/Device/Details/Summary/{device.Id}"
if(data['76689'] != airwatchlink):
glpiapi.UpdateAirwatchLink(deviceID, airwatchlink)
# filtre des plateformes # filtre des plateformes
if(platformFilterEnabled): if(platformFilterEnabled):
if device.PlatformId in platformFilterOut: if device.PlatformId in platformFilterOut:

39
start.sh Normal file
View File

@ -0,0 +1,39 @@
#!/bin/bash
python /pre-start.py
if [ $CONF != '' ]; then
case $TASK in
syncGLPI)
python3 /airwatchConnector/sync.py -c $CONF -f
;;
stagingAssignment)
python3 /airwatchConnector/stagingUserAssignation.py -c $CONF -f
;;
*)
exit 0
;;
esac
else
confFiles = $(ls /airwatchConnector/conf/*.conf)
if [ $confFiles = '' ]; then exit 1; do
for CONF in $confFiles; do
case $TASK in
syncGLPI)
python3 /airwatchConnector/sync.py -c $CONF -for
;;
stagingAssignment)
python3 /airwatchConnector/stagingUserAssignation.py -c $CONF -f
;;
*)
exit 0
;;
esac
done
fi