lumnik. / scénarios Tous les scénarios · English

SOAP WCF et WS-Security: le mot de passe a tourné, le Fault le dit

Objectif Lire un service WCF protégé par un UsernameToken, survivre à la rotation de son mot de passe, et être prévenu d'une commande confirmée après sa date promise.

Un distributeur expose ses commandes ouvertes par un service WCF: HTTPS sous la CA de l'entreprise, un UsernameToken dans l'enveloppe.

orders-20261105.tsv
OrderNo	Customer	OrderDate	PromisedDate	ConfirmedDate	Warehouse	Amount
CV-26-0001	Auto Bérard	2026-10-02	2026-10-24	2026-10-23	Valence	509.25
CV-26-0002	Pneus du Sud	2026-10-03	2026-10-26	2026-10-24	Grenoble	898.50
CV-26-0003	Carrosserie Lefèvre	2026-10-04	2026-10-28	2026-10-25	Lyon	1287.75
CV-26-0004	Moto Évasion	2026-10-05	2026-10-30	2026-10-30	Valence	1676.00
CV-26-0005	Cycles Gaëtan	2026-10-06	2026-11-01	2026-10-31	Grenoble	2065.25
CV-26-0006	Dépannage Noël	2026-10-07	2026-11-03	2026-11-01	Lyon	2454.50
CV-26-0007	Agri-Méca	2026-10-08	2026-11-05	2026-11-02	Lyon	2843.75
CV-26-0008	Poids Lourds Rhône	2026-10-09	2026-11-07	2026-11-07	Grenoble	3232.00
CV-26-0009	Taxi Côte	2026-10-10	2026-10-31	2026-10-30	Lyon	3621.25
CV-26-0010	Location Hébert	2026-10-11	2026-11-02	2026-10-31	Valence	4010.50
CV-26-0011	Électro-Diesel	2026-10-12	2026-11-04	2026-11-01	Grenoble	399.75
CV-26-0012	Garage Martin	2026-10-13	2026-11-06	2026-11-06	Lyon	788.00
CV-26-0013	Auto Bérard	2026-10-14	2026-11-08	2026-11-07	Valence	1177.25
CV-26-0014	Pneus du Sud	2026-10-15	2026-11-10	2026-11-08	Grenoble	1566.50
CV-26-0015	Carrosserie Lefèvre	2026-10-16	2026-11-12	2026-11-09	Lyon	1955.75
CV-26-0016	Moto Évasion	2026-10-17	2026-11-14	2026-11-14	Lyon	2344.00
CV-26-0017	Cycles Gaëtan	2026-10-18	2026-11-16	2026-11-15	Grenoble	2733.25
CV-26-0018	Dépannage Noël	2026-10-19	2026-11-09	2026-11-07	Lyon	3122.50
CV-26-0019	Agri-Méca	2026-10-20	2026-11-11	2026-11-08	Valence	3511.75

… 11 lignes de plus

manifests/10-orders.yaml
# A distributor's order service on WCF: BasicHttpBinding over HTTPS (TransportWithMessageCredential), the caller# authenticated by a WS-Security UsernameToken inside the envelope. The certificate is signed by the company's own# CA, trusted for this connector only. The password lives in lm secret, never in the manifest.apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-wcf-a  connector_type: rest-generic  scopes: [sales-wcf-a]spec:  base_url: https://wcf-orders:8443  tls:    ca_secret: SCENARIO_WCF_CA_A          # lm secret set SCENARIO_WCF_CA_A - < ca.crt  auth:    kind: none                            # no HTTP auth: the credential travels in the envelope  endpoints:    - id: open-orders      kind: soap      path: /OrderService.svc      soap_action: "http://distrib.example/orders/IOrderService/GetOpenOrders"      header: |        <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">          <wsse:UsernameToken>            <wsse:Username>lumnik-reader</wsse:Username>            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">{{secret:SCENARIO_WCF_PASSWORD_A}}</wsse:Password>          </wsse:UsernameToken>        </wsse:Security>      body: |        <GetOpenOrders xmlns="http://distrib.example/orders">          <page>{{page}}</page>          <pageSize>{{page_size}}</pageSize>        </GetOpenOrders>      pagination:        kind: soap        page_size: 10        last_page_path: "$.Body.GetOpenOrdersResponse.GetOpenOrdersResult.IsLastPage"        response_path: "$.Body.GetOpenOrdersResponse.GetOpenOrdersResult.Orders.Order"      target:        kind: hybrid        table: ext.scenario_wcf_a        unique_key: "$.OrderNo"        promote:          - { name: order_no, path: "$.OrderNo", type: text }          - { name: customer, path: "$.Customer", type: text }          - { name: promised_date, path: "$.PromisedDate", type: date }          - { name: confirmed_date, path: "$.ConfirmedDate", type: date }          - { name: warehouse, path: "$.Warehouse", type: text }          - { name: amount, path: "$.Amount", type: decimal }
manifests/20-confirmed-late.yaml
apiVersion: apps.lumnik.io/v1kind: Processmetadata:  name: scenario-wcf-late-aspec:  scope: sales-wcf-a  on: { event: data.row.changed, table: ext.scenario_wcf_a }  when:    - "confirmed_date>{promised_date}"     # the warehouse confirms a ship date past the customer's promise  emit:    type: decision.confirmed-late-a    message: "{order_no} {customer}: promised {promised_date}, confirmed {confirmed_date} by {warehouse} — call the customer before they call"    key: order_no
$ lm source rest test scenario-wcf-a
ENDPOINT     METHOD  STATUS  ITEMS  VERDICT
open-orders  POST    -       0      I/O error (IOException)
Error: 1 of 1 enabled endpoint(s) did not answer with readable data

$ lm connector run scenario-wcf-a
run scheduled: f3eb3ee1-c4ee-4993-a2eb-b9da719be202

$ lm run get f3eb3ee1-c4ee-4993-a2eb-b9da719be202
ID        CONNECTOR       ENDPOINT     STATUS  IN  OUT  SKIPPED  DEAD-LETTERS  STARTED           ENDED             EVALUATED
f3eb3ee1  scenario-wcf-a  open-orders  Failed  0   0    0        0             2026-11-07 00:16  2026-11-07 00:17  yes
✖ run failed: I/O error (IOException)
manifests/11-orders-http11.yaml
# The same connector, speaking HTTP/1.1: the service answers a refused credential with its Fault, then# resets the HTTP/2 stream, and over HTTP/2 the answer is lost on the way (#1859).apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-wcf-a  connector_type: rest-generic  scopes: [sales-wcf-a]spec:  base_url: https://wcf-orders:8443  http_version: "1.1"                     # Kestrel resets an HTTP/2 stream after a Fault: ask for HTTP/1.1  tls:    ca_secret: SCENARIO_WCF_CA_A          # lm secret set SCENARIO_WCF_CA_A - < ca.crt  auth:    kind: none                            # no HTTP auth: the credential travels in the envelope  endpoints:    - id: open-orders      kind: soap      path: /OrderService.svc      soap_action: "http://distrib.example/orders/IOrderService/GetOpenOrders"      header: |        <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">          <wsse:UsernameToken>            <wsse:Username>lumnik-reader</wsse:Username>            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">{{secret:SCENARIO_WCF_PASSWORD_A}}</wsse:Password>          </wsse:UsernameToken>        </wsse:Security>      body: |        <GetOpenOrders xmlns="http://distrib.example/orders">          <page>{{page}}</page>          <pageSize>{{page_size}}</pageSize>        </GetOpenOrders>      pagination:        kind: soap        page_size: 10        last_page_path: "$.Body.GetOpenOrdersResponse.GetOpenOrdersResult.IsLastPage"        response_path: "$.Body.GetOpenOrdersResponse.GetOpenOrdersResult.Orders.Order"      target:        kind: hybrid        table: ext.scenario_wcf_a        unique_key: "$.OrderNo"        promote:          - { name: order_no, path: "$.OrderNo", type: text }          - { name: customer, path: "$.Customer", type: text }          - { name: promised_date, path: "$.PromisedDate", type: date }          - { name: confirmed_date, path: "$.ConfirmedDate", type: date }          - { name: warehouse, path: "$.Warehouse", type: text }          - { name: amount, path: "$.Amount", type: decimal }
orders-20261106.tsv
OrderNo	Customer	OrderDate	PromisedDate	ConfirmedDate	Warehouse	Amount
CV-26-0001	Auto Bérard	2026-10-02	2026-10-24	2026-10-23	Valence	509.25
CV-26-0002	Pneus du Sud	2026-10-03	2026-10-26	2026-10-24	Grenoble	898.50
CV-26-0004	Moto Évasion	2026-10-05	2026-10-30	2026-10-30	Valence	1676.00
CV-26-0005	Cycles Gaëtan	2026-10-06	2026-11-01	2026-10-31	Grenoble	2065.25
CV-26-0006	Dépannage Noël	2026-10-07	2026-11-03	2026-11-01	Lyon	2454.50
CV-26-0007	Agri-Méca	2026-10-08	2026-11-05	2026-11-14	Lyon	2843.75
CV-26-0008	Poids Lourds Rhône	2026-10-09	2026-11-07	2026-11-07	Grenoble	3232.00
CV-26-0009	Taxi Côte	2026-10-10	2026-10-31	2026-10-30	Lyon	3621.25
CV-26-0010	Location Hébert	2026-10-11	2026-11-02	2026-10-31	Valence	4010.50
CV-26-0012	Garage Martin	2026-10-13	2026-11-06	2026-11-06	Lyon	788.00
CV-26-0013	Auto Bérard	2026-10-14	2026-11-08	2026-11-07	Valence	1177.25
CV-26-0014	Pneus du Sud	2026-10-15	2026-11-10	2026-11-08	Grenoble	1566.50
CV-26-0015	Carrosserie Lefèvre	2026-10-16	2026-11-12	2026-11-09	Lyon	1955.75
CV-26-0016	Moto Évasion	2026-10-17	2026-11-14	2026-11-18	Lyon	2344.00
CV-26-0017	Cycles Gaëtan	2026-10-18	2026-11-16	2026-11-15	Grenoble	2733.25
CV-26-0018	Dépannage Noël	2026-10-19	2026-11-09	2026-11-07	Lyon	3122.50
CV-26-0019	Agri-Méca	2026-10-20	2026-11-11	2026-11-08	Valence	3511.75
CV-26-0020	Poids Lourds Rhône	2026-10-21	2026-11-13	2026-11-13	Grenoble	3900.00
CV-26-0021	Taxi Côte	2026-10-22	2026-11-15	2026-11-14	Lyon	289.25

… 9 lignes de plus

Limites

  • Joué contre CoreWCF sous Kestrel. Un WCF .NET Framework sous IIS n'a pas été joué: sa tolérance à un UsernameToken sans Timestamp n'est pas vérifiée ici.
  • Le mot de passe part en clair dans l'enveloppe (PasswordText), protégé par HTTPS. Un PasswordDigest demande un nonce et une date à chaque appel: aucun placeholder ne les fabrique.
  • La pré-requête lit HTTP 500 là où le run nomme le Fault (#1861).
  • SOAP 1.1 seulement: un service en SOAP 1.2 (wsHttpBinding) n'est pas lu.
  • Commandes, clients et mots de passe synthétiques.

Ventes · WCF

Les commandes vivent derrière un service WCF.

SOAP, HTTPS, un UsernameToken dans l'enveloppe.

<wsse:UsernameToken>

Le problème

Le mot de passe a tourné. Le hub disait: I/O error.

  • Le service avait répondu son Fault, puis coupé le flux HTTP/2.
  • Trois tentatives pour un refus d'identifiants.

Ce qui n'est pas modifié

Le service reste tel quel.

  • Son binding, sa CA, son contrat.
  • Aucun proxy, aucun code .NET.

Ce qu'il a fallu écrire

http_version: "1.1"

Une ligne dans le connecteur.

  • Le Fault arrive entier.
  • Il n'est pas réessayé.

Ce qui a été corrigé

La version HTTP se choisit par connecteur (#1859).

  • Nouveau secret, même manifeste: la lecture reprend.
  • Trois commandes en retard: trois décisions.

Essayer

Le scénario se rejoue en local.

Chaque étape est publiée avec sa sortie.

Ce scénario tourne sur lumnik Open, gratuit et open source.

Essayer lumnik Open →