SOAP security
WS-Security, credentials in the message body and mutual TLS - configured per connection and combined as each endpoint requires.
SOAP endpoints authenticate clients at three levels: the SOAP header, the message body and the TLS layer. Many endpoints require more than one of them. Zato configures each mechanism separately, and a connection can combine any of them:
- WS-Security definitions cover everything that goes into the
wsse:Securityheader: UsernameToken, X.509 signing and encryption, SAML assertions - Body credentials put a username and password inside the business message itself
- Mutual TLS presents a client certificate at the transport layer
Security is applied by the platform when invoke runs. The service code is the same regardless of the mechanisms that a connection uses.
The examples on this page continue the immunization registry from the calling tutorial.
WS-Security definitions
A WS-Security definition holds the credentials and crypto material once, and any number of connections refer to it by name.
Step 1. In the Dashboard, navigate to Security > WS-Security, click Create a new WS-Security definition and fill in the form:
- Name:
Registry Credentials - Username:
my-registry-user - Mode:
Username token - Click OK
Step 2. The definition is created with a random password - click Change password in its row and set the real one. Passwords are never part of the create or edit forms, so they never linger in browser autofill or form history.
Step 3. Open the outgoing SOAP connection under Connections > Outgoing > SOAP, switch to the Security tab, pick WS-Security/Registry Credentials from the security dropdown and click OK.
From this moment every invoke through the connection includes the WS-Security header. The service code is unchanged from the version without security:
request = SOAPMessage()
request.namespace = 'urn:cdc:iisb:2011'
request.echoBack = 'Hello registry'
response = self.soap['Immunization Registry'].invoke('connectivityTest', request)
The mode of the definition determines what goes into the header.
UsernameToken
The username and password are sent in a wsse:UsernameToken header element - the most common corporate SOAP setup. The Password digest switch on the Username token tab sends the password in digest form, with an auto-generated nonce and timestamp, for endpoints that require it. Without it, the password is sent as text over TLS.
X.509
The Crypto material tab holds PEM material and two switches:
- Sign - the message is signed with your private key (Signing key), and your Signing certificate chain is included so the receiver can verify the signature
- Encrypt - the body is encrypted for the receiver using their Peer certificate
For verifying and decrypting what comes back, the definition also holds a Decryption key and Trust anchors - the CA certificates that peers' signatures are validated against. All of it is standard XML-DSig and XML-Encryption, the same W3C mechanics that WS-Security prescribes.
SAML
The definition generates a sender-vouches SAML 2.0 assertion on each request, built from the fields of the SAML tab:
- Issuer - who vouches for the assertion, e.g.
https://my-hospital.example.com - Subject - the user or system the assertion is about
- Audience - who the assertion is meant for, e.g. the exchange's community identifier
With Sign enabled in the Crypto material tab, the assertion includes an enveloped RSA-SHA256 signature computed with the same Signing key and Signing certificate chain fields the X.509 mode uses, as healthcare document-exchange profiles such as IHE XUA require of cross-community requests.
Externally issued assertions - tokens obtained from an identity provider rather than generated by the platform - can also be attached as raw bytes and pass through unmodified.
Body credentials
Some endpoints do not look at the SOAP header at all - they expect the username and password as the first elements of the business message. Immunization registries following the CDC WSDL are the best-known example:
<sub:submitSingleMessage>
<sub:username>my-registry-user</sub:username>
<sub:password>*******</sub:password>
<sub:hl7Message>MSH|^~\&amp;|...</sub:hl7Message>
</sub:submitSingleMessage>
Step 1. Open the connection and switch to the Body credentials tab.
Step 2. Add two rows, with the Element name username in the first and password in the second.
Step 3. Click OK.
Rows without a position become the first children of the operation in row order, which matches the layout above. For an endpoint that requires a credential elsewhere, set a 1-based Position on its row.
The values come from the security definition attached to the connection, and injection happens inside invoke, after your service has built the message - so the credentials never reach the service's code and cannot appear in its logs:
# The service only builds business fields ..
request = SOAPMessage()
request.namespace = 'urn:cdc:iisb:2011'
request.hl7Message = hl7_payload
# .. username and password are injected here, per the connection's mapping.
response = self.soap['Registry Submit'].invoke('submitSingleMessage', request)
Because the injected elements inherit the operation's namespace, they look to the receiving system exactly like any other field of the message. A new password set through Change password takes effect on the next call, with no change to the connection or the code.
Mutual TLS
Endpoints that authenticate clients at the TLS layer are covered by two fields on the connection's Security tab:
- Client certificate - a local path to a PEM file with the certificate, or with the certificate and key combined, e.g.
/opt/zato/certs/client-cert.pem - Client key - a local path to the key's PEM file, when it is kept separately, e.g.
/opt/zato/certs/client-key.pem
The paths point to files on the server - in containerized deployments, certificates are mounted into the container and the fields name the mount paths. Because this is a connection-level setting and not a security-definition type, it combines freely with everything above: one connection can present a client certificate and inject body credentials and sign its messages, which is precisely what some registries and national networks require.
The files named in Client certificate and Client key can be replaced while the server is running, in the same way as the files of an mTLS definition - when they are kept under /opt/hot-deploy/ssl, e.g. in a host directory mapped to /opt/hot-deploy/ssl/outgoing, the connection discards its pooled TLS connections each time one of the files changes, and the next call presents the new certificate.
TLS certificate verification
The TLS validation dropdown on the same tab controls whether the outgoing SOAP connection verifies the certificate of the remote server. In enmasse this is the tls_verify key, which defaults to true.
Combine security mechanisms
| Requirement | Configuration |
|---|---|
| Username and password in the SOAP header | WS-Security definition, UsernameToken mode |
| Username and password inside the message body | Any definition with credentials + Body credentials rows |
| Signed messages | WS-Security definition, X.509 mode with Sign |
| Encrypted messages | WS-Security definition, X.509 mode with Encrypt |
| Signed SAML user assertion | WS-Security definition, SAML mode with Sign |
| Client certificate at the TLS layer | Client certificate and key paths on the connection |
| Any combination of the above | Attach the definition and set the connection fields together |
Configuration with enmasse
Every definition and connection on this page is also plain YAML, deployed with enmasse. The UsernameToken definition and the connection with body credentials and a client certificate:
security:
- name: Registry Credentials
username: my-registry-user
password: Zato_Enmasse_Env.RegistryPassword
type: wss
mode: username_token
use_digest: true
outgoing_soap:
- name: Registry Submit
host: https://registry.example.gov
url_path: /iisb/services
security: Registry Credentials
soap_action: urn:cdc:iisb:2011:submitSingleMessage
soap_version: "1.2"
timeout: 30
tls_client_cert: /opt/zato/certs/client-cert.pem
tls_client_key: /opt/zato/certs/client-key.pem
body_credentials:
- name: username
- name: password
The Zato_Enmasse_Env. prefix reads the password from an environment variable, so no secret is ever committed to the repository.
An X.509 definition holds its switches and PEM material inline:
security:
- name: Registry Signing
username: my-registry-user
type: wss
mode: x509
sign: true
encrypt: true
signing_key: |-
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
signing_certificate_chain: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
decryption_key: |-
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
peer_certificate: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
trust_anchors: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
A SAML definition holds the assertion fields, plus the signing material when the assertion is to be signed:
security:
- name: Exchange Assertion
username: my-registry-user
type: wss
mode: saml
issuer: https://my-hospital.example.com
subject: CN=Dr Smith,O=Example Hospital
audience: urn:exchange:community
sign: true
signing_key: |-
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
signing_certificate_chain: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
Import the file in the Dashboard under System → Config → Import enmasse, or mount it under /opt/hot-deploy/enmasse/enmasse.yaml inside the container to have it imported on start. The enmasse reference documents every field of all three modes.