Skip to content

Commit

Permalink
feat(iot-actions): iot rule https action l2 construct (aws#25535)
Browse files Browse the repository at this point in the history
Implemented the L2 construct for the IoT Core HTTPS action.


Closes aws#25491 .

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
cponfick authored and bmoffatt committed Jul 28, 2023
1 parent 75c47de commit 0cac95a
Show file tree
Hide file tree
Showing 13 changed files with 820 additions and 0 deletions.
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-iot-actions-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Currently supported are:
- Publish messages on SNS topics
- Write messages into columns of DynamoDB
- Put messages IoT Events input
- Send messages to HTTPS endpoints

## Republish a message to another MQTT topic

Expand Down Expand Up @@ -346,3 +347,28 @@ const topicRule = new iot.TopicRule(this, 'TopicRule', {
],
});
```

## Send Messages to HTTPS Endpoints

The code snippet below creates an AWS IoT Rule that sends messages
to an HTTPS endpoint when it is triggered:

```ts
const topicRule = new iot.TopicRule(this, 'TopicRule', {
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'",
),
});

topicRule.addAction(
new actions.HttpsAction('https://example.com/endpoint', {
confirmationUrl: 'https://example.com',
headers: [
{ key: 'key0', value: 'value0' },
{ key: 'key1', value: 'value1' },
],
auth: { serviceName: 'serviceName', signingRegion: 'us-east-1' },
}),
);
}
```
96 changes: 96 additions & 0 deletions packages/@aws-cdk/aws-iot-actions-alpha/lib/https-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as iot from '@aws-cdk/aws-iot-alpha';
import * as iam from 'aws-cdk-lib/aws-iam';
import { CommonActionProps } from './common-action-props';
import { singletonActionRole } from './private/role';

export interface HttpActionSigV4Auth {
/**
* The service name.
*/
readonly serviceName: string;
/**
* The signing region.
*/
readonly signingRegion: string;
}

export interface HttpActionHeader {
/**
* The HTTP header key.
*/
readonly key: string;
/**
* The HTTP header value. Substitution templates are supported.
*/
readonly value: string;
}

/**
* Configuration properties of an HTTPS action.
*
* @see https://docs.aws.amazon.com/iot/latest/developerguide/https-rule-action.html
*/
export interface HttpsActionProps extends CommonActionProps {
/**
* If specified, AWS IoT uses the confirmation URL to create a matching topic rule destination.
*/
readonly confirmationUrl?: string;

/**
* The headers to include in the HTTPS request to the endpoint.
*/
readonly headers?: Array<HttpActionHeader>;

/**
* Use Sigv4 authorization.
*/
readonly auth?: HttpActionSigV4Auth;
}

/**
* The action to send data from an MQTT message to a web application or service.
*/
export class HttpsAction implements iot.IAction {
private readonly role?: iam.IRole;
private readonly url: string;
private readonly confirmationUrl?: string;
private readonly headers?: Array<HttpActionHeader>;
private readonly auth?: HttpActionSigV4Auth;

/**
* @param url The url to which to send post request.
* @param props Optional properties to not use default.
*/
constructor( url: string, props: HttpsActionProps={}) {
this.url = url;
this.confirmationUrl = props.confirmationUrl;
this.headers = props.headers;
this.role = props.role;
this.auth = props.auth;
}

/**
* @internal
*/
public _bind(topicRule: iot.ITopicRule): iot.ActionConfig {
const role = this.role ?? singletonActionRole(topicRule);
const sigV4 = this.auth ? {
sigv4: {
roleArn: role.roleArn,
serviceName: this.auth.serviceName,
signingRegion: this.auth.signingRegion,
},
} : this.auth;

return {
configuration: {
http: {
url: this.url,
confirmationUrl: this.confirmationUrl,
headers: this.headers,
auth: sigV4,
},
},
};
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-iot-actions-alpha/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ export * from './lambda-function-action';
export * from './s3-put-object-action';
export * from './sqs-queue-action';
export * from './sns-topic-action';
export * from './https-action';
export * from './step-functions-state-machine-action';

Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Template, Match } from 'aws-cdk-lib/assertions';
import * as iot from '@aws-cdk/aws-iot-alpha';
import * as cdk from 'aws-cdk-lib';
import * as actions from '../../lib';

test('Default HTTPS action', () => {
// GIVEN
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id FROM 'device/+/data'",
),
});
const expectedUrl = 'https://example.com';

// WHEN
topicRule.addAction(new actions.HttpsAction(expectedUrl));

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
Http: {
Url: expectedUrl,
},
},
],
},
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'iot.amazonaws.com',
},
},
],
Version: '2012-10-17',
},
});
});

test('can set confirmation url', () => {
// GIVEN
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id FROM 'device/+/data'",
),
});
const expectedUrl = 'https://example.com';
const expectedConfirmationUrl = 'https://example.com/confirm';

// WHEN
topicRule.addAction(
new actions.HttpsAction(expectedUrl, {
confirmationUrl: expectedConfirmationUrl,
}),
);

//THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
Http: {
Url: expectedUrl,
ConfirmationUrl: expectedConfirmationUrl,
},
},
],
},
});
});

test('can set http headers', () => {
// GIVEN
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id FROM 'device/+/data'",
),
});
const expectedUrl = 'https://example.com';
const headers = [
{ key: 'key0', value: 'value0' },
{ key: 'key1', value: 'value1' },
];

// WHEN
topicRule.addAction(
new actions.HttpsAction(expectedUrl, { headers: headers }),
);

//THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
Http: {
Url: expectedUrl,
Headers: [
{ Key: 'key0', Value: 'value0' },
{ Key: 'key1', Value: 'value1' },
],
},
},
],
},
});
});

test('can set http auth', () => {
// GIVEN
const stack = new cdk.Stack();
const topicRule = new iot.TopicRule(stack, 'MyTopicRule', {
sql: iot.IotSql.fromStringAsVer20160323(
"SELECT topic(2) as device_id FROM 'device/+/data'",
),
});
const expectedUrl = 'https://example.com';
const expectedAuth = {
serviceName: 'serviceName',
signingRegion: 'signingName',
};

// WHEN
topicRule.addAction(
new actions.HttpsAction(expectedUrl, { auth: expectedAuth }),
);

//THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', {
TopicRulePayload: {
Actions: [
{
Http: {
Url: expectedUrl,
Auth: {
Sigv4: {
RoleArn: {
'Fn::GetAtt': [
Match.stringLikeRegexp('MyTopicRuleTopicRuleActionRole'),
'Arn',
],
},
ServiceName: expectedAuth.serviceName,
SigningRegion: expectedAuth.signingRegion,
},
},
},
},
],
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "31.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "IoTHttpsActionDefaultTestDeployAssert019947CA.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "31.0.0",
"files": {
"ceb4c33f2a2723481892f941c88433106a7019ed281d49385fdbaf9f0b09a343": {
"source": {
"path": "IoTHttpsActionTestStack.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "ceb4c33f2a2723481892f941c88433106a7019ed281d49385fdbaf9f0b09a343.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading

0 comments on commit 0cac95a

Please sign in to comment.