L’OPC UA (Open Platform Communications Unified Architecture) est une technologie en pleine expansion, notamment dans le milieu industriel. Après une courte introduction sur le sujet, cet article détaillera comment en déployer une implémentation open source dans un environnement embarqué à travers quelques exemples.
L’OPC UA est un ensemble de spécifications dont l’objectif est de standardiser les échanges entre équipements. Historiquement très répandu dans le milieu industriel, en particulier dans les automates et la supervision, il fait ces dernières années une percée dans d’autres domaines comme le ferroviaire, le nucléaire ou le spatial.
L’OPC UA se positionne comme le successeur d’OPC DA (OPC Data Access) qui avait l’inconvénient d’être basé sur une technologie propriétaire Microsoft (COM/DCOM), qui est de surcroît devenue obsolète.
La fondation OPC [1] est chargée des évolutions de la technologie via des groupes de travaux ainsi que de sa promotion.
Parmi ses propriétés les plus remarquables, nous pouvons lister :
- son indépendance vis-à-vis des plateformes, ce qui permet de le déployer d’un petit capteur actionneur jusqu’à un serveur en passant par le cloud ;
- des capacités de modélisation de données avancées qui permettent de modéliser finement la sémantique métier des données ;
- des fonctionnalités de cybersécurité avancées (signature, chiffrement, autorisations…). Ces fonctionnalités sont régulièrement évaluées par le BSI (équivalent allemand de l’ANSSI). Celui-ci a jugé que le protocole offrait un haut niveau de sécurité à ses utilisateurs [2] ;
- et surtout, son haut niveau d’interopérabilité garanti par la fondation, qui évalue à l’aide d’un laboratoire indépendant tous les produits souhaitant être certifiés OPC UA.
La spécification OPC UA, normalisée IEC62541, est conséquente [3] (1500 pages de normes en 14 volumes pour la version 1.04). Elle s’enrichit régulièrement, notamment avec ses dernières évolutions comme le Pub/Sub qui permet d’étendre ses cas d’usage à la connectivité cloud et aux bus de terrain.
Par conséquent, les différentes implémentations disponibles ne couvrent généralement pas l’intégralité du standard, mais se concentrent sur un sous-ensemble de fonctionnalités adapté à leurs cas d’usage principaux.
Dans cet article, les exemples sont basés sur S2OPC [4], implémentation open source Apache 2.0. Écrite en C99, elle est bien adaptée au développement embarqué et aux contraintes en termes de cybersécurité et/ou de sûreté de fonctionnement.
Enfin, vous aurez sûrement remarqué que depuis le début de cet article, nous évitons soigneusement d’utiliser le qualificatif « protocole » pour l’OPC UA. En effet, ses spécifications contiennent des parties fonctionnelles et sémantiques qui vont bien au-delà de la définition usuelle du terme protocole. C’est pourquoi beaucoup d’experts OPC UA considèrent qu’au lieu de ce terme réducteur, il vaut mieux parler de technologie ou de « framework ». Comme nous n’avons envie de nous fâcher avec personne, c’est la dernière fois que vous voyez le mot protocole écrit dans cet article ;-).
1. Présentation technique
L’OPC UA se décline en deux variantes :
- le client/serveur, mode connecté point à point. La plus classique et bien adaptée à la supervision ;
- le Pub/Sub, mode déconnecté basé sur le mécanisme du Publish/Subscribe. Permettant d’adresser à la fois les bus de terrain et la connectivité cloud.
Dans cet article, nous allons nous concentrer sur la partie client-serveur. La mise en œuvre du Pub/Sub pourra faire l’objet d’un second article. Le couplage de celui-ci avec la technologie TSN permet notamment d’atteindre un très haut niveau de déterminisme sur les communications.
En mode client-serveur, un serveur stocke et organise les données dans une structure que l’on appelle l’« address space ». Celle-ci est constituée de nœuds reliés entre eux par des références. Les nœuds possèdent un certain nombre d’attributs suivant leur type. Par exemple, les nœuds de type « variable » possèdent un attribut contenant leur valeur.
Le client a quant à lui la possibilité d’ouvrir des connexions sur les différents points d’accès du serveur (ou endpoints). De manière simplifiée, nous pouvons considérer que ces connexions se font en trois étapes :
- l’ouverture d’un « secure channel » qui permet de signer et chiffrer les messages ;
- la création d’une session qui permet une authentification mutuelle des applications client et serveur ;
- l’activation de cette session qui permet une authentification au niveau utilisateur.
Une fois la connexion établie, le client pourra ensuite envoyer au serveur des requêtes afin que celui-ci exécute les services et lui renvoie la réponse. Les services les plus courants sont les suivants :
- « GetEndpoints » : ce service permet à un client de connaître quel sont les endpoints d’un serveur et quelles sont les contraintes pour pouvoir ouvrir une session avec chacun d’entre eux (politique de sécurité, authentification utilisateur…) ;
- « Browse » : ce service permet de parcourir les références entre les différents nœuds de l’« address space » afin d’en découvrir la structure ;
- « Read » : ce service permet d’aller lire de manière synchrone un ou plusieurs attributs d’un ou plusieurs nœuds de l’« address space » ;
- « Write » : ce service permet d’aller écrire de manière synchrone un ou plusieurs attributs d’un ou plusieurs nœuds de l’« address space » ;
- « Method call » : ce service permet d’appeler des méthodes de manière synchrone appartenant à des objets de l’« address space » ;
- « Subscription » : cet ensemble de services permet à un client de s’abonner à une donnée afin d’être notifié de manière asynchrone d’une modification de celle-ci.
Au niveau de la sécurité des échanges, trois modes sont possibles :
- None : aucune protection ;
- Sign : l’intégrité des données est garantie par une signature ;
- SignAndEncrypt : les données sont signées et chiffrées.
Parallèlement à cela, la configuration d’un point d’accès comprend également la politique de sécurité qui définit l’ensemble des algorithmes et des tailles des clés et certificats qui vont être utilisés pour l’établissement d’une session client-serveur.
La PKI associée est constituée d’une paire clé privée / clé publique pour le serveur et le client. Les clés publiques sont signées par une autorité de certification que tous deux reconnaissent. Lors des premiers échanges, le client va vérifier la signature du certificat du serveur et le serveur fera la même chose avec le certificat du client.
Il existe également des mécanismes d’authentification et de droits utilisateurs, mais ceux-ci ne seront pas abordés dans cet article pour des raisons de concision.
De nombreuses configurations sont possibles. Dans un objectif pédagogique, cet article se concentre sur le déploiement d’une communication client-serveur entre :
- une carte microprocesseur Raspberry Pi 3 B+ 32 bits sous GNU/Linux embarqué ;
- une carte microcontrôleur STM32 Nucleo H745ZI-Q de chez STMicroelectronics sous Zephyr disposant entre autres d’une interface Ethernet lui permettant de s’interfacer avec le Raspberry Pi.
Dans cet article, nous nous reposons en partie sur l’outil Docker qui est supposé être installé sur votre environnement de développement (Docker Desktop pour les personnes utilisant GNU/Linux sous WSL2).
Pour information ou rappel, les commandes suivantes permettent d’autoriser Docker à exécuter des commandes sans sudo explicite :
2. Mise en œuvre sur Raspberry Pi / Linux
Commençons par cloner le dépôt des sources.
Plusieurs solutions sont possibles pour la compilation. L’une d’entre elles consiste à compiler directement sur le Raspberry Pi en suivant les instructions du wiki [5]. La seconde que nous allons suivre dans cet article est d’utiliser une image Docker contenant les outils et les dépendances permettant de faire une compilation croisée de x64 vers ARM 32 bits. Récupérons l’image Docker.
Puis lançons la compilation croisée en activant l’ensemble des fonctionnalités du serveur :
Les résultats ont été générés dans le sous-répertoire build_toolchain/bin. Vous pouvez les transférer sur votre Raspberry Pi dans un dossier dédié :
Le serveur de démonstration est configurable via trois fichiers XML vers lesquels doivent pointer trois variables d’environnement :
- TEST_SERVER_XML_ADDRESS_SPACE qui définit le contenu de l’« address space ». Le format est normalisé par la fondation OPC ;
- TEST_SERVER_XML_CONFIG qui contient la définition des différents points de connexion du serveur (les endpoints) ;
- TEST_USERS_XML_CONFIG qui contient la définition de l’authentification au niveau utilisateur.
Pour utiliser la configuration d’exemple, positionnez les variables d’environnement, puis lancez les serveurs :
Félicitations, votre premier serveur OPC UA a démarré.
En ce qui concerne la partie client, le même répertoire contient un certain nombre de binaires de démonstration faisant chacun appel à un service OPC UA en tant que client. Par exemple, s2opc_discovery envoie une requête « GetEndpoints » alors que s2opc_read envoie comme son nom l’indique une requête de « Read ».
3. Mise en œuvre sur Zephyr / STM32 Nucleo
Passons maintenant à la partie microcontrôleur sur carte STM32 Nucleo H745ZI-Q avec l’OS Zephyr.
De même que pour la partie Raspberry Pi, il est possible d’installer les outils Zephyr à partir de ce lien [6].
Dans cet article, nous utilisons une image Docker contenant l’ensemble de ces outils ainsi que leurs dépendances installées :
On lance un conteneur auquel on lie le répertoire où l’on va ranger les binaires générés :
Dans ce docker, nous retrouvons les sources de Zephyr et de S2OPC.
Il est nécessaire d’obtenir la branche Zephyr de S2OPC. Cette étape se fait grâce à west.
Via des variables d’environnement, on choisit l’application ainsi que la carte sur laquelle on veut exécuter l’application.
Puis on lance la compilation croisée du projet Zephyr et de l’application pour la carte. Une fois la compilation terminée, on copie le fichier dans le répertoire partagé.
Note : l’instruction sudo est nécessaire ici, car l’utilisateur dans le container Docker n’a pas les droits d’écriture sur votre système de fichier.
On branche maintenant un câble USB entre le connecteur CN11 de notre carte STM32 NUCLEO et notre PC.
Puis on flashe la mémoire de la carte en la branchant à l’ordinateur via le câble USB et en copiant le fichier binaire dans le média.
Ces instructions permettent de construire une image contenant un serveur OPC UA. Pour construire un client, il suffit de changer la variable d’environnement, puis de lancer la construction de l’application.
Vérifions maintenant si le serveur démarre et connectons-nous au terminal série de la carte :
Si l’on appuie sur le bouton reset de la carte, nous voyons les traces suivantes qui nous confirment que le serveur OPC UA a bien démarré :
4. Intégration du client et du serveur
Nous allons maintenant utiliser un client OPC UA sur la carte Raspberry Pi pour faire appel à quelques services sur le serveur de la carte STM32 Nucleo.
Nous savons grâce aux traces ci-dessus que le serveur a démarré sur la carte STM32 Nucleo à l’URL opc.tcp://192.168.42.21:4841. Cette adresse est modifiable si besoin dans la configuration du projet.
Nous laissons à l’utilisateur le soin de placer le Raspberry Pi et la STM32 Nucleo sur le même sous-réseau local.
Utilisons maintenant le service GetEndpoints pour savoir quels sont les points de connexion disponibles sur le serveur :
Quatre points de connexion sont listés, mais nous allons nous intéresser à deux d’entre eux à des fins de démonstration :
- le premier qui n’a ni chiffrement ni signature ;
- le dernier qui est chiffré et signé avec la politique de sécurité Basic256Sha256.
Commençons par parcourir les références associées au nœud objet dont l’identifiant est normalisé par i=85 :
Nous voyons un nœud dont l’identifiant est ns=1;s=PubVars dont le nom laisse supposer qu’il peut contenir des variables. Parcourons donc le niveau inférieur :
En effet, le dossier contient un certain nombre de variables de différents types qui vont nous permettre d’effectuer quelques tests de lecture et écriture. Prenons par exemple le nom dont l’identifiant est ns=1;s=PubString et de type chaîne de caractères.
Nous envoyons une requête de lecture en mode « None » sur ce nœud et nous demandons l’attribut numéro 13 (celui de la valeur) :
Essayons maintenant de modifier l’attribut valeur de ce nœud via une requête d’écriture :
La réponse à la requête contient un StatusCode de valeur 0, ce qui confirme que l’opération est un succès.
Une nouvelle demande de lecture nous permet de vérifier que le changement de valeur a bien été pris en compte :
Les opérations précédentes ont été réalisées sans signature ni chiffrement des échanges. Comme nous l’avons vu plus haut, le serveur expose d’autres « endpoints » incluant de la sécurité. En passant par ceux-ci, il est possible de réaliser les mêmes opérations que précédemment.
Par exemple, la même requête de lecture en signé et chiffré peut être envoyée comme suit :
Ces binaires OPC UA client de démonstration utilisent tous la politique de sécurité Basic256Sha256, ce qui correspond à un « endpoint » du serveur que nous avons découvert.
Les sources de S2OPC contiennent deux jeux de clés/certificats pour le client et le serveur qui diffèrent sur la longueur de la clé privée : 2048 bits pour le premier et 4096 pour le second. Les clients de démonstration utilisent par défaut la version 4K (ou 4096) alors que le serveur est programmé avec le 2K (ou 2048). C’est pourquoi nous précisions dans la ligne de commande que le client doit utiliser la version 2K.
Un point intéressant à noter est le fait que Wireshark possède nativement un dissecteur OPC UA qui est bien utile pour diagnostiquer les échanges entre un client et un serveur. Voyez par exemple ce qui se passe lors de la précédente lecture de nœud en clair.
Cela nous permet de visualiser les différentes étapes :
- ouverture d’une « secure channel » ;
- ouverture d’une session ;
- appel au service de lecture ;
- fermeture de la session ;
- fermeture du secure channel.
Si vous réalisez l’opération identique en mode signé et chiffré, vous obtenez les traces suivantes.
Vous remarquez que tous les échanges à partir de l’ouverture du « secure channel » sont identifiés comme « UA Secure Conversation Message ». Ceci est logique, car tous ces échanges sont chiffrés à notre demande et Wireshark ne possède pas les informations nécessaires pour décoder ces messages.
Conclusion
Vous avez pu découvrir dans cet article une introduction rapide au framework OPC UA ainsi que quelques exemples de mise en œuvre. Le sujet de l’OPC UA est si vaste que nous n’avons pu voir qu’une petite partie des concepts. J’invite les personnes intéressées par un approfondissement à consulter les ressources mises en ligne par la fondation OPC [1].
Les ressources mises en œuvre dans cet article sont disponibles sur le GitLab du projet [2]. Nous vous encourageons à vous approprier les différentes applications de démonstration mises à disposition. Et pour les plus courageux d’entre vous, nous accueillons toujours avec plaisir de nouveaux contributeurs.
À toutes fins utiles, j’aimerais enfin signaler que les applications utilisées dans cet article ont été développées avec un unique objectif de démonstration. Dans le cadre d’un déploiement industriel, d’autres aspects sont à prendre en compte, notamment vis-à-vis des contraintes de cybersécurité (création, déploiement, stockage et renouvellement des certificats, gestion des utilisateurs…).
Références
[1] Fondation OPC : https://opcfoundation.org
[2] Analyse du BSI : https://opcfoundation.org/security/
[3] Spécifications du protocole OPC UA : https://opcfoundation.org/developer-tools/documents/
[4] Base GitLab du projet S2OPC : https://gitlab.com/systerel/S2OPC
[5] Wiki du projet S2OPC : https://gitlab.com/systerel/S2OPC/-/wikis/compilation#compilation-on-linux
[6] Projet Zephyr : https://docs.zephyrproject.org/latest/getting_started/index.html