Chapitre 09
OPC-UA au shell
Objectifs du chapitre
- Comprendre les étapes d'une connexion OPC-UA sur
opc.tcp: de la poignée de main Hello/Acknowledge jusqu'à la session activée. - Découvrir depuis le shell les endpoints qu'un serveur publie et leurs
politiques de sécurité, avec les outils CLI de la lib
asyncua. - Naviguer l'espace d'adressage et lire un nĆud, en ligne de commande ou en quelques lignes de Python.
- Surtout : diagnostiquer pourquoi une session ne s'Ă©tablit pas â certificat non approuvĂ©, politique incompatible, port fermĂ© â en capturant la poignĂ©e de main et en lisant le code de statut exact.
1. OPC-UA en bref
OPC-UA (Unified Architecture) est le protocole d'échange applicatif standard entre un automate,
une supervision et le monde IT. Il fonctionne en client/serveur : notre
CPU S7-1500 expose un serveur OPC-UA, et notre poste de diagnostic joue le
client. Le transport par dĂ©faut est opc.tcp â un protocole binaire
(UA-TCP) posé directement sur TCP, sur le port 4840. Ce n'est pas du HTTP : on ne le
lira pas avec curl, mais on peut parfaitement l'observer et le dialoguer au shell.
Une connexion ne s'ouvre pas d'un coup : elle se construit en plusieurs couches empilĂ©es, chacune pouvant Ă©chouer pour sa propre raison. C'est prĂ©cisĂ©ment ce qui fait la richesse â et la difficultĂ© â du diagnostic OPC-UA. Les Ă©tapes :
- Hello / Acknowledge â la couche transport UA-TCP se met d'accord sur les tailles de buffer et la version. Si ça passe, le port 4840 rĂ©pond bien un serveur OPC-UA.
- OpenSecureChannel â Ă©tablissement du canal sĂ©curisĂ© : c'est ici que la Security Policy et les certificats entrent en jeu. PremiĂšre Ă©tape qui peut ĂȘtre rejetĂ©e pour raison de sĂ©curitĂ©.
- CreateSession â le serveur crĂ©e une session applicative et renvoie son certificat.
- ActivateSession â le client s'authentifie (anonyme, login/mot de passe, ou certificat utilisateur) et active la session.
- Services â une fois la session active :
Browse(naviguer),Read,Write, souscriptionsâŠ
Retiens cet ordre : oĂč la connexion s'arrĂȘte dans cette sĂ©quence te dit pourquoi elle Ă©choue.
ServiceFault (rouge), avant mĂȘme la session. Si le canal passe, session (vert) puis services
(bleu) s'enchaßnent.2. La sécurité OPC-UA, source n°1 de pannes
Si une seule chose est à retenir de ce chapitre, c'est celle-ci : la grande majorité des connexions OPC-UA qui « ne marchent pas » échouent sur la sécurité, pas sur le réseau. Deux réglages indépendants décrivent le niveau de protection d'un endpoint :
- Security Policy â la suite cryptographique :
None(aucune),Basic256Sha256,Aes128_Sha256_RsaOaep,Aes256_Sha256_RsaPss⊠Elle fixe les algorithmes de signature et de chiffrement. - Message Security Mode â ce qu'on fait des messages :
None(rien),Sign(signés, intÚgres mais lisibles),SignAndEncrypt(signés et chiffrés).
DĂšs qu'on quitte le mode None, l'authentification applicative repose sur des
certificats X.509 mutuels. Le client présente son certificat au serveur, le serveur
présente le sien au client, et chacun doit faire confiance au certificat de l'autre.
C'est un rejet réciproque : si le certificat du client n'est pas dans la liste de confiance
du serveur, le serveur refuse â mĂȘme si le mot de passe est bon, mĂȘme si le rĂ©seau est parfait. C'est
le piĂšge classique du premier raccordement.
Deux erreurs indépendantes se ressemblent sur le terrain : « le serveur refuse ma politique »
(BadSecurityPolicyRejected) et « le serveur ne fait pas confiance à mon certificat »
(BadCertificateUntrusted / BadSecurityChecksFailed). La premiĂšre se rĂšgle en
choisissant une politique publiée par le serveur ; la seconde en approuvant le certificat cÎté
serveur. Ne les confonds pas.
3. Découvrir les endpoints du serveur
Avant de se battre avec des certificats, la premiĂšre question est : que le serveur
accepte-t-il ? Un serveur OPC-UA publie une liste d'endpoints, chacun avec sa
Security Policy et son Security Mode. La lib asyncua (installée au chapitre 1) fournit
des outils CLI pour ça, sans écrire une ligne de code. Le plus utile est uadiscover :
$ uadiscover opc.tcp://192.168.0.1:4840
# Application: urn:S7-1500:OPCUA:Server (ProductUri: SIEMENS.S71500)
Endpoints:
opc.tcp://192.168.0.1:4840
SecurityPolicy : None
SecurityMode : None
UserTokens : Anonymous, UserName
opc.tcp://192.168.0.1:4840
SecurityPolicy : Basic256Sha256
SecurityMode : SignAndEncrypt
UserTokens : UserName, Certificate
opc.tcp://192.168.0.1:4840
SecurityPolicy : Aes128_Sha256_RsaOaep
SecurityMode : SignAndEncrypt
UserTokens : UserName, Certificate
Cette sortie est une mine d'informations. On y lit que le serveur expose un endpoint ouvert
(None/None, souvent désactivé sur une vraie install), et deux endpoints sécurisés.
Elle répond aussi à la question « quelle authentification ? » via UserTokens : anonyme,
login/mot de passe (UserName), ou certificat utilisateur. Choisis toujours une combinaison
publiĂ©e ici â en demander une autre, c'est le BadSecurityPolicyRejected assurĂ©.
Lance uadiscover en tout premier. Il ne demande aucun certificat, ne
crée aucune session, et te dit exactement ce que le serveur sait faire. Tu évites des heures à te battre
avec une politique ou un token que le CPU n'accepte de toute façon pas. Découvre d'abord, connecte
ensuite.
4. Naviguer et lire un nĆud
Une fois l'endpoint choisi, deux besoins : parcourir l'espace d'adressage pour trouver les nĆuds,
et lire leur valeur. En restant sur l'endpoint None (le plus simple pour débuter), les outils
CLI d'asyncua suffisent :
$ uals -u opc.tcp://192.168.0.1:4840
# browse depuis Objects (ns=0;i=85)
Objects
ââ Server
ââ DeviceSet
ââ PLC_1
ââ DataBlocksGlobal
â ââ Moteur1 (ns=3)
â ââ Vitesse ns=3;i=1001
â ââ Marche ns=3;i=1002
ââ ...
Le nĆud Moteur1.Vitesse porte l'identifiant ns=3;i=1001 (espace de noms 3,
identifiant numĂ©rique 1001 â la convention Siemens pour les variables issues d'un bloc de donnĂ©es). Pour
lire sa valeur :
$ uaread -u opc.tcp://192.168.0.1:4840 -n "ns=3;i=1001"
1450.0
Pour aller plus loin (boucler, formater, intégrer à un script de supervision), quelques lignes de Python
avec asyncua font le mĂȘme travail et se prĂȘtent Ă l'automatisation :
import asyncio
from asyncua import Client
async def main():
async with Client("opc.tcp://192.168.0.1:4840") as c:
node = c.get_node("ns=3;i=1001")
print(await node.read_value())
asyncio.run(main())
Sur l'endpoint None, aucun certificat n'est requis : la lecture marche du premier coup
si le port est ouvert. DÚs qu'on vise un endpoint sécurisé, il faut fournir un certificat client
(paramĂštre set_security cĂŽtĂ© asyncua) â et c'est lĂ que commencent les vraies
aventures du §6.
Lire est sans danger ; Ă©crire ne l'est pas. Un uawrite sur un nĆud
comme Moteur1.Marche agit rĂ©ellement sur le procĂ©dĂ© â tu peux dĂ©marrer un moteur.
Sur une installation vivante, contente-toi de lectures tant que tu n'as pas la certitude absolue de ce
que fait le nĆud visĂ©. Lecture d'abord, toujours.
5. Capturer la poignée de main
Quand une connexion échoue et que les messages du client restent vagues, la vérité est sur le fil.
tshark décode nativement OPC-UA : on filtre le port 4840 en capture, et on n'affiche que
les trames OPC-UA en analyse :
$ sudo tshark -i eth0 -f 'tcp port 4840' -Y opcua
1 0.000 192.168.0.71 â 192.168.0.1 OpcUa Hello message
2 0.002 192.168.0.1 â 192.168.0.71 OpcUa Acknowledge message
3 0.004 192.168.0.71 â 192.168.0.1 OpcUa OpenSecureChannel request
4 0.021 192.168.0.1 â 192.168.0.71 OpcUa ServiceFault : BadCertificateUntrusted
Cette capture raconte toute l'histoire : Hello et Acknowledge passent â donc le
port est ouvert et un serveur OPC-UA répond bien. Mais dÚs l'OpenSecureChannel, le serveur renvoie
un ServiceFault avec le code BadCertificateUntrusted : il ne fait pas
confiance au certificat client. On n'atteint jamais CreateSession. Le code de statut est le
diagnostic ; voici les plus fréquents :
| Code de statut | Cause probable | Correction |
|---|---|---|
BadCertificateUntrustedBadSecurityChecksFailed |
Certificat client non approuvé cÎté serveur. | Approuver le certificat client dans la liste de confiance du CPU (TIA). |
BadSecurityPolicyRejected |
Politique demandée non supportée par le serveur. | Choisir une politique publiée par uadiscover. |
BadIdentityTokenRejected |
Mode d'authentification refusé (ex. anonyme interdit). | Utiliser un UserToken autorisé (login ou certificat). |
BadTimeout / connexion refusée(aucune trame en retour) |
Port 4840 fermé, pare-feu, ou serveur OPC-UA désactivé. | Vérifier l'ouverture du port et le pare-feu (§6a). |
Le réflexe gagnant : reproduis la tentative qui échoue pendant que tshark -Y opcua
tourne, et lis le code de statut exact du ServiceFault. C'est lui qui
tranche entre « problĂšme de certificat », « problĂšme de politique » et « problĂšme de rĂ©seau » â bien
plus fiable qu'un message d'erreur générique cÎté client.
6. Arbre de diagnostic « la session ne s'établit pas »
Face à une connexion qui refuse de s'ouvrir, on remonte la pile dans l'ordre, du plus bas au plus haut. Chaque étape écarte une famille de causes.
a. Le port 4840 est-il seulement ouvert ?
Inutile de parler certificats si rien n'écoute. On confirme d'abord la couche transport avec
nmap (ou ss cÎté serveur, cf. chapitre 5) :
$ nmap -p4840 192.168.0.1
PORT STATE SERVICE
4840/tcp open opcua
Si le port est filtered (pare-feu) ou closed (serveur OPC-UA désactivé dans le
CPU), le problĂšme est lĂ : aucune poignĂ©e de main ne pourra mĂȘme commencer. C'est cohĂ©rent avec un
BadTimeout ou un « connexion refusée » sans aucune trame de retour.
b. Le serveur publie-t-il une politique compatible ?
Le port est ouvert : on interroge les endpoints avec uadiscover (§3) et on vérifie que
la politique et le mode qu'on veut utiliser figurent bien dans la liste. Un BadSecurityPolicyRejected
à la capture confirme qu'on demande quelque chose d'absent ; on aligne son client sur ce qui est publié.
c. Le certificat client est-il approuvé ?
La politique est bonne mais la capture montre BadCertificateUntrusted Ă l'OpenSecureChannel :
le serveur voit notre certificat mais ne lui fait pas confiance. La correction ne se fait pas au shell mais
cÎté serveur : il faut ajouter le certificat client à la liste de confiance du CPU
(voir l'encart TIA ci-dessous). AprĂšs approbation, la mĂȘme tentative franchit l'OpenSecureChannel.
d. L'URL d'endpoint est-elle résoluble par le client ?
Cas plus subtil : la découverte réussit, mais la connexion échoue juste aprÚs avec un timeout ou une
erreur de résolution. C'est que le serveur, quand on l'interroge, renvoie ses endpoints avec son
propre hostname (ex. opc.tcp://plc1-s71500:4840) au lieu de son IP. Si le
client ne résout pas ce nom (pas de DNS, pas d'entrée /etc/hosts), il n'arrive nulle part. La
parade : forcer l'IP.
Un serveur OPC-UA renvoie souvent ses endpoints avec son hostname, pas son IP. Le
client suit alors cette URL⊠et se perd si le nom n'est pas résoluble sur le réseau OT (souvent sans
DNS). SymptĂŽme : uadiscover marche mais uals/uaread
temporise. Deux parades : ajouter le nom dans /etc/hosts
(192.168.0.1 plc1-s71500), ou dire au client d'ignorer l'URL renvoyée et de garder l'IP
d'origine â l'option --no-check-hostname des outils asyncua, ou
client.check_hostname = False en Python.
Sur le S7-1500, le serveur OPC-UA s'active et se paramĂštre dans TIA Portal (propriĂ©tĂ©s du CPU â
OPC UA â Serveur) : activation, port, endpoints, Security Policies proposĂ©es, gestion des
utilisateurs. Surtout, c'est lĂ que se trouve la liste de confiance des certificats
(SĂ©curitĂ© de la communication â Certificats des partenaires). Pour lever un
BadCertificateUntrusted, on y importe et on approuve le certificat du client,
puis on recharge la configuration. Sans cette approbation, aucun canal sécurisé ne s'ouvrira, quoi que
fasse le shell.
Récapitulatif
- OPC-UA = client/serveur sur
opc.tcp(binaire UA-TCP), port 4840 ; le CPU S7-1500 est le serveur. Connexion en Ă©tapes : Hello/Ack â OpenSecureChannel â CreateSession â ActivateSession â services. - La sĂ©curitĂ© est la panne n°1 : Security Policy (None, Basic256Sha256âŠ) Ă Message Security Mode (None, Sign, SignAndEncrypt), plus des certificats X.509 mutuels qui doivent ĂȘtre approuvĂ©s de part et d'autre.
uadiscover opc.tcp://192.168.0.1:4840liste endpoints, politiques et modes en premier : on sait ce que le serveur accepte avant de tenter quoi que ce soit.- Lire :
uals(browse),uaread -n "ns=3;i=1001", ou un court scriptasyncua. Ăcrire agit sur le procĂ©dĂ© â lecture d'abord. - Diagnostic « pas de session » :
tshark -Y opcuapour lire le code de statut â port fermĂ© (nmap -p4840), politique (BadSecurityPolicyRejected), certificat (BadCertificateUntrustedâ approuver dans TIA), ou hostname non rĂ©soluble (forcer l'IP).
Exercices
Exercice 1 â Cartographier ce que le serveur accepte
Interroge le serveur OPC-UA du CPU (opc.tcp://192.168.0.1:4840) et dresse la liste des
endpoints publiés : pour chacun, quelle Security Policy, quel Security Mode, et quels modes
d'authentification. Quel endpoint choisirais-tu pour un premier test rapide, et pourquoi ?
Voir la solution
$ uadiscover opc.tcp://192.168.0.1:4840
Endpoints:
opc.tcp://192.168.0.1:4840 None / None (Anonymous, UserName)
opc.tcp://192.168.0.1:4840 Basic256Sha256 / SignAndEncrypt (UserName, Certificate)
opc.tcp://192.168.0.1:4840 Aes128_Sha256_RsaOaep / SignAndEncrypt (UserName, Certificate)
Pour un premier test, l'endpoint None/None en authentification
anonyme est le plus rapide : aucun certificat, aucune politique à négocier. S'il
répond, on a validé le réseau, le port et le serveur d'un coup. Attention : sur une vraie
installation cet endpoint est souvent dĂ©sactivĂ© pour raison de sĂ©curitĂ© â auquel cas il faudra passer
directement par un endpoint sécurisé et gérer les certificats.
Exercice 2 â Diagnostiquer une session qui n'aboutit pas
Un collÚgue se plaint : « ma connexion OPC-UA sécurisée ne s'ouvre pas, le client dit juste connection failed ». Le port 4840 répond pourtant. Capture sa tentative, identifie le code de statut, et explique la correction.
Voir la solution
On capture pendant qu'il relance sa connexion :
$ sudo tshark -i eth0 -f 'tcp port 4840' -Y opcua
1 0.000 .71 â .1 OpcUa Hello message
2 0.002 .1 â .71 OpcUa Acknowledge message
3 0.004 .71 â .1 OpcUa OpenSecureChannel request
4 0.019 .1 â .71 OpcUa ServiceFault : BadCertificateUntrusted
Hello/Acknowledge passent : le port est bien ouvert et un serveur OPC-UA rĂ©pond â donc
ce n'est pas un problÚme réseau. La connexion casse à l'OpenSecureChannel avec
BadCertificateUntrusted : le serveur reçoit le certificat du client mais ne lui fait
pas confiance. Correction : cÎté CPU, dans TIA Portal
(SĂ©curitĂ© de la communication â Certificats des partenaires), importer et
approuver le certificat client, puis recharger. Rien à changer cÎté shell : le
mĂȘme client repassera ensuite l'OpenSecureChannel. Si le code avait Ă©tĂ©
BadSecurityPolicyRejected, la correction aurait Ă©tĂ© tout autre â aligner la politique du
client sur celles publiées par uadiscover.
Exercice 3 â Lire une valeur, proprement
Lis la valeur du nĆud ns=3;i=1001 du CPU. Avant de tenter la lecture, vĂ©rifie que le
port 4840 est bien ouvert â pour ne pas confondre « nĆud introuvable » et « serveur injoignable ». Donne
les deux commandes, puis l'équivalent en Python.
Voir la solution
D'abord la couche transport, ensuite l'application :
$ nmap -p4840 192.168.0.1
4840/tcp open opcua
$ uaread -u opc.tcp://192.168.0.1:4840 -n "ns=3;i=1001"
1450.0
Le port open écarte le pare-feu et le serveur désactivé ; si uaread
Ă©chouait ensuite, on saurait que le problĂšme est applicatif (nĆud, politique, certificat), pas
réseau. L'équivalent Python, pratique pour scripter une supervision :
import asyncio
from asyncua import Client
async def main():
async with Client("opc.tcp://192.168.0.1:4840") as c:
node = c.get_node("ns=3;i=1001")
print(await node.read_value())
asyncio.run(main())
Si le serveur renvoie ses endpoints avec son hostname et que la lecture temporise malgré un port
ouvert, ajoute c.check_hostname = False avant l'entrée dans le with, ou une
entrée dans /etc/hosts (cf. le piÚge du §6d).