# opc-plc behind a port NAT — the #806/#813 fixture for NatPublishedEndpointBrowseTests # (tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.IntegrationTests, Fixture=opc-plc-nat). # # The point of this stack is what the server ADVERTISES, not what it simulates. opc-plc builds its # GetEndpoints EndpointUrl from its PLC hostname (--ph; default = the container's short hostname) + its # listen port, so with # * a private hostname that cannot resolve from any client (named explicitly via --ph: `.invalid` is # reserved by RFC 6761 and guaranteed never to resolve, so no resolver search domain or wildcard can make # the premise pass; the container `hostname:` alone would advertise the bare short name), and # * a published host port (50010) different from the internal listen port (50000), # every advertised endpoint is opc.tcp://otopcua-opc-plc-nat-private.invalid:50000/ — unreachable on BOTH host # and port. A client that follows the advertised URL fails DNS; one that keeps the operator-configured # host:port (OpcUaEndpointUrlRewriter) reaches the server. The tests assert that premise first. # # A SEPARATE stack from ../docker-compose.yml (deployed to /opt/otopcua-opcuaclient-nat, not # /opt/otopcua-opcuaclient) so bringing it up/down never touches the plain otopcua-opc-plc fixture. # # rsync -av tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/nat/ \ # dohertj2@10.100.0.35:/opt/otopcua-opcuaclient-nat/ # ssh dohertj2@10.100.0.35 'cd /opt/otopcua-opcuaclient-nat && docker compose up -d' # OPCUA_NAT_ENDPOINT=opc.tcp://10.100.0.35:50010 dotnet test \ # tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.IntegrationTests --filter Fixture=opc-plc-nat # # #818: the server's PKI (/app/pki — own cert + private key, and the client certs --aa accepted) lives in the # named volume `opc-plc-nat-pki`, so recreating the container keeps the SAME server certificate. Its PUBLIC half is # committed next to this file (opc-plc-nat-server.der) and the secure tests trust it in a throwaway browse PKI. The # private key never leaves the volume. To roll the certificate deliberately (e.g. at expiry, 2027-11-10): # ssh dohertj2@10.100.0.35 'cd /opt/otopcua-opcuaclient-nat && docker compose down -v && docker compose up -d' # ssh dohertj2@10.100.0.35 'docker exec otopcua-opc-plc-nat bash -c "cat /app/pki/own/certs/*.der"' \ # > tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/nat/opc-plc-nat-server.der # (until the committed copy is refreshed the positive secure tests SKIP on a thumbprint mismatch — they never pass # against a certificate other than the committed one). services: opc-plc-nat: image: mcr.microsoft.com/iotedge/opc-plc:2.14.10 container_name: otopcua-opc-plc-nat hostname: otopcua-opc-plc-nat-private restart: unless-stopped labels: project: lmxopcua ports: # published 50010 -> internal 50000: the port half of the NAT. Do NOT make these equal. - "50010:50000" command: - "--pn=50000" # --ph: the host part of every advertised EndpointUrl (and the server cert's DNS name). - "--ph=otopcua-opc-plc-nat-private.invalid" # --ut: also advertise SecurityPolicy=None (the secure policies stay advertised; the secure test uses them). - "--ut" # --aa: accept the browser's self-signed client cert. Trust in the OTHER direction (the browser trusting # this server's cert) is deliberately not automatic — that is what the secure test pins. - "--aa" volumes: - opc-plc-nat-pki:/app/pki # The image runs as uid 1654 ("app") and ships no /app/pki, so Docker creates the volume mount point root-owned # and opc-plc could not write its certificate there. A throwaway test simulator: run it as root rather than add a # chown init container. user: "0" healthcheck: # The image ships no netstat (the sibling ../docker-compose.yml healthcheck was permanently "unhealthy" for that # reason until #817 switched it to this probe too); bash's /dev/tcp is there. test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/50000"] interval: 5s timeout: 2s retries: 10 start_period: 10s volumes: opc-plc-nat-pki: