You use HMAC signing to authenticate Ditto at external services like AWS and Azure without transmitting credentials directly.
credentials.type to "hmac" and specify an algorithm (aws4-hmac-sha256, az-monitor-2016-04-01, or az-sasl) with the required parameters. Supported on HTTP Push and AMQP 1.0 connections.Overview
Ditto provides an extensible framework for HMAC-based signing authentication for HTTP Push and AMQP 1.0 connections. Three algorithms are available out of the box:
| Algorithm | Connection types | Use case |
|---|---|---|
aws4-hmac-sha256 |
HTTP Push | AWS Version 4 signing (SNS, S3, etc.) |
az-monitor-2016-04-01 |
HTTP Push | Azure Monitor Data Collector |
az-sasl |
HTTP Push, AMQP 1.0 | Azure IoT Hub and Azure Service Bus Shared Access Signatures |
How it works
Set the credentials field in your connection configuration:
{
"connectionType": "http-push",
"uri": "https://...:443",
"credentials": {
"type": "hmac",
"algorithm": "<algorithm>",
"parameters": {
"...": "..."
}
}
}
Configuration
aws4-hmac-sha256
For AWS services using Version 4 request signing:
| Parameter | Description | Default |
|---|---|---|
region |
AWS region | – |
service |
AWS service name | – |
accessKey |
IAM access key | – |
secretKey |
IAM secret key | – |
doubleEncode |
Double-encode path segments (false for S3, true for others) |
true |
canonicalHeaders |
Header names to include in the signature | ["host"] |
xAmzContentSha256 |
S3 payload hash header: EXCLUDED, INCLUDED, or UNSIGNED |
EXCLUDED |
az-monitor-2016-04-01
For Azure Monitor Data Collector:
| Parameter | Description |
|---|---|
workspaceId |
Azure Monitor workspace ID |
sharedKey |
Primary or secondary workspace key |
az-sasl
For Azure IoT Hub and Azure Service Bus Shared Access Signatures:
| Parameter | Description |
|---|---|
sharedKeyName |
Name of the shared access policy |
sharedKey |
Primary or secondary key (Base64-encode for Service Bus) |
endpoint |
Resource URI (for example, myHub.azure-devices.net or https://myNs.servicebus.windows.net/myQueue) |
ttl (optional) |
Signature lifetime (for example, 10m). For AMQP connections, the broker closes the connection after TTL and Ditto reconnects. Default: 7 days. |
Examples
AWS SNS
Publish twin events to an AWS SNS topic:
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "AWS SNS",
"connectionType": "http-push",
"connectionStatus": "open",
"uri": "https://sns.<aws-region>.amazonaws.com:443",
"credentials": {
"type": "hmac",
"algorithm": "aws4-hmac-sha256",
"parameters": {
"region": "<aws-region>",
"service": "sns",
"accessKey": "<aws-access-key>",
"secretKey": "<aws-secret-key>"
}
},
"sources": [],
"targets": [{
"address": "GET:/",
"topics": ["_/_/things/twin/events"],
"authorizationContext": ["integration:ditto"],
"payloadMapping": ["javascript"]
}],
"specificConfig": { "parallelism": "1" },
"mappingDefinitions": {
"javascript": {
"mappingEngine": "JavaScript",
"options": {
"incomingScript": "function mapToDittoProtocolMsg() { return undefined; }",
"outgoingScript": "function mapFromDittoProtocolMsg(namespace,name,group,channel,criterion,action,path,dittoHeaders,value,status,extra) {\n let textPayload = JSON.stringify(Ditto.buildDittoProtocolMsg(namespace, name, group, channel, criterion, action, path, dittoHeaders, value, status, extra));\n let query = 'Action=Publish&Message=' + encodeURIComponent(textPayload) + '&Subject=ThingModified&TopicArn=<sns-topic-arn>';\n return Ditto.buildExternalMsg({\"http.query\":query},'',null,'text/plain');\n}"
}
}
}
}
The outgoing mapper builds SNS query parameters and sets them via the http.query special header:
function mapFromDittoProtocolMsg(namespace, name, group, channel, criterion,
action, path, dittoHeaders, value, status, extra) {
let textPayload = JSON.stringify(Ditto.buildDittoProtocolMsg(namespace, name,
group, channel, criterion, action, path, dittoHeaders, value, status, extra));
let query = 'Action=Publish&Message=' + encodeURIComponent(textPayload) +
'&Subject=ThingModified&TopicArn=<sns-topic-arn>';
return Ditto.buildExternalMsg({ "http.query": query }, '', null, 'text/plain');
}
AWS S3
Publish twin events as objects in an AWS S3 bucket. Note the S3-specific
parameters doubleEncode: false and xAmzContentSha256: "INCLUDED":
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "AWS S3",
"connectionType": "http-push",
"connectionStatus": "open",
"uri": "https://<s3-bucket>.s3.<aws-region>.amazonaws.com:443",
"credentials": {
"type": "hmac",
"algorithm": "aws4-hmac-sha256",
"parameters": {
"region": "<aws-region>",
"service": "s3",
"accessKey": "<aws-access-key>",
"secretKey": "<aws-secret-key>",
"doubleEncode": false,
"canonicalHeaders": ["host", "x-amz-date"],
"xAmzContentSha256": "INCLUDED"
}
},
"sources": [],
"targets": [{
"address": "PUT:/",
"topics": ["_/_/things/twin/events"],
"authorizationContext": ["integration:ditto"],
"payloadMapping": ["javascript"]
}],
"specificConfig": { "parallelism": "1" },
"mappingDefinitions": {
"javascript": {
"mappingEngine": "JavaScript",
"options": {
"incomingScript": "function mapToDittoProtocolMsg() { return undefined; }",
"outgoingScript": "function mapFromDittoProtocolMsgWrapper(msg) {\n let topic = msg['topic'].split('/').join(':');\n let headers = { 'http.path': topic+':'+msg['revision'] };\n return Ditto.buildExternalMsg(headers, JSON.stringify(msg), null, 'application/json');\n}"
}
}
}
}
The payload mapper creates a distinct S3 object per event by computing the path via the
special header http.path.
It overrides mapFromDittoProtocolMsgWrapper instead of mapFromDittoProtocolMsg to access the revision number.
The object name consists of the event topic with / replaced by : followed by its revision (for example,
<namespace>:<name>:things:twin:events:modified:42):
function mapFromDittoProtocolMsgWrapper(msg) {
let topic = msg['topic'].split('/').join(':');
let headers = {
'http.path': topic + ':' + msg['revision']
};
let textPayload = JSON.stringify(msg);
let bytePayload = null;
let contentType = 'application/json';
return Ditto.buildExternalMsg(
headers,
textPayload,
bytePayload,
contentType
);
}
Azure Monitor Data Collector
Push twin events to Azure Monitor:
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "Azure Monitor",
"connectionType": "http-push",
"connectionStatus": "open",
"uri": "https://<workspace-id>.ods.opinsights.azure.com:443",
"credentials": {
"type": "hmac",
"algorithm": "az-monitor-2016-04-01",
"parameters": {
"workspaceId": "<workspace-id>",
"sharedKey": "<shared-key>"
}
},
"sources": [],
"targets": [{
"address": "POST:/api/logs?api-version=2016-04-01",
"topics": ["_/_/things/twin/events"],
"authorizationContext": ["integration:ditto"],
"headerMapping": {
"Content-Type": "application/json",
"Log-Type": "TwinEvent"
}
}],
"specificConfig": { "parallelism": "1" }
}
Azure IoT Hub (HTTP)
Forward live messages as direct method calls to Azure IoT Hub:
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "Azure IoT Hub HTTP",
"connectionType": "http-push",
"connectionStatus": "open",
"uri": "https://<hostname>:443",
"credentials": {
"type": "hmac",
"algorithm": "az-sasl",
"parameters": {
"sharedKeyName": "<policy-name>",
"sharedKey": "<shared-key>",
"endpoint": "<hostname>"
}
},
"sources": [],
"targets": [{
"address": "POST:/twins/{{ thing:id }}/methods?api-version=2018-06-30",
"topics": ["_/_/things/live/messages"],
"authorizationContext": ["integration:ditto"],
"issuedAcknowledgementLabel": "live-response",
"payloadMapping": ["javascript"]
}],
"specificConfig": { "parallelism": "1" },
"mappingDefinitions": {
"javascript": {
"mappingEngine": "JavaScript",
"options": {
"incomingScript": "function mapToDittoProtocolMsg() { return undefined; }",
"outgoingScript": "function mapFromDittoProtocolMsg(namespace,name,group,channel,criterion,action,path,dittoHeaders,value,status,extra) {\n let payload = { \"methodName\": action, \"responseTimeoutInSeconds\": parseInt(dittoHeaders.timeout), \"payload\": value };\n return Ditto.buildExternalMsg(dittoHeaders, JSON.stringify(payload), null, 'application/json');\n}"
}
}
}
}
The outgoing JavaScript mapper transforms the Ditto Protocol message into the
direct method format,
using the live message subject as methodName, the timeout as responseTimeoutInSeconds, and the value as payload:
function mapFromDittoProtocolMsg(
namespace,
name,
group,
channel,
criterion,
action,
path,
dittoHeaders,
value,
status,
extra
) {
let headers = dittoHeaders;
let payload = {
"methodName": action,
"responseTimeoutInSeconds": parseInt(dittoHeaders.timeout),
"payload": value
};
let textPayload = JSON.stringify(payload);
let bytePayload = null;
let contentType = 'application/json';
return Ditto.buildExternalMsg(
headers,
textPayload,
bytePayload,
contentType
);
}
Azure IoT Hub direct methods require this JSON format:
{
"methodName": "reboot",
"responseTimeoutInSeconds": 200,
"payload": {
"input1": "someInput",
"input2": "anotherInput"
}
}
Send live messages to a Thing containing the expected payload as value, the expected methodName as live message
subject, and the expected responseTimeoutInSeconds as timeout. For example, invoke the above direct method with a
POST to <ditto>/api/2/things/<thing-id>/inbox/messages/reboot?timeout=200s with body:
{
"input1": "someInput",
"input2": "anotherInput"
}
Azure IoT Hub (AMQP)
Forward live messages as Cloud-to-Device messages via AMQP 1.0:
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "Azure IoT Hub AMQP",
"connectionType": "amqp-10",
"connectionStatus": "open",
"uri": "amqps://<hostname>:5671",
"credentials": {
"type": "hmac",
"algorithm": "az-sasl",
"parameters": {
"sharedKeyName": "<policy-name>",
"sharedKey": "<shared-key>",
"endpoint": "<hostname>"
}
},
"sources": [],
"targets": [{
"address": "/messages/devicebound",
"topics": ["_/_/things/live/messages"],
"authorizationContext": ["integration:ditto"],
"headerMapping": {
"iothub-ack": "full",
"to": "/devices/{{thing:id}}/messages/devicebound"
}
}]
}
Azure Service Bus (HTTP)
Forward live messages to an Azure Service Bus queue:
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "Azure Service Bus HTTP",
"connectionType": "http-push",
"connectionStatus": "open",
"uri": "https://<hostname>:443",
"credentials": {
"type": "hmac",
"algorithm": "az-sasl",
"parameters": {
"sharedKeyName": "<policy-name>",
"sharedKey": "<base64-encoded-key>",
"endpoint": "https://<hostname>/<queue-name>"
}
},
"sources": [],
"targets": [{
"address": "POST:/<queue-name>/messages",
"topics": ["_/_/things/live/messages"],
"authorizationContext": ["integration:ditto"],
"issuedAcknowledgementLabel": "live-response"
}],
"specificConfig": { "parallelism": "1" }
}
For Azure Service Bus, the sharedKey must be additionally Base64-encoded (for example, theKey
becomes dGhlS2V5).
Custom algorithms
You can add custom signing algorithms by implementing the appropriate interface and registering it in connectivity.conf:
| Connection type | Config path | Interface |
|---|---|---|
| HTTP Push | ditto.connectivity.connection.http-push.hmac-algorithms |
HttpRequestSigningFactory |
| AMQP 1.0 | ditto.connectivity.connection.amqp10.hmac-algorithms |
AmqpConnectionSigningFactory |
The default configuration registers the pre-defined algorithms as follows:
ditto.connectivity.connection {
http-push.hmac-algorithms = {
aws4-hmac-sha256 =
"org.eclipse.ditto.connectivity.service.messaging.httppush.AwsRequestSigningFactory"
az-monitor-2016-04-01 =
"org.eclipse.ditto.connectivity.service.messaging.httppush.AzMonitorRequestSigningFactory"
az-sasl =
"org.eclipse.ditto.connectivity.service.messaging.signing.AzSaslSigningFactory"
// my-own-request-signing-algorithm =
// "my.package.MyOwnImplementationOfHttpRequestSigningFactory"
}
amqp10.hmac-algorithms = {
az-sasl =
"org.eclipse.ditto.connectivity.service.messaging.signing.AzSaslSigningFactory"
// my-own-connection-signing-algorithm =
// "my.package.MyOwnImplementationOfAmqpConnectionSigningFactory"
}
}
Further reading
- HTTP 1.1 binding – HTTP push connection details
- AMQP 1.0 binding – AMQP 1.0 connection details
- Connections overview – connection model and configuration