Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iot-actions): add SNS publish action #18839

Merged
merged 19 commits into from
Feb 9, 2022

Conversation

AdamVD
Copy link
Contributor

@AdamVD AdamVD commented Feb 5, 2022

First-time contributor 👋

fixes #17700


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@gitpod-io
Copy link

gitpod-io bot commented Feb 5, 2022

* @param props Properties to configure the action.
*/
constructor(topic: sns.ITopic, props: SnsTopicActionProps = {}) {
if (topic.topicName.endsWith('.fifo')) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion probably needed here.

On the implementation: I don't like having to check for the ".fifo" suffix directly, but ITopic doesn't expose anything to check if it is FIFO (unlike IQueue.fifo). However, since the suffix is mandated by the service this may be adequate until a better option comes available.

On whether or not the error should even be thrown: The docs for this action clearly state that FIFO topics are not supported due to the distributed nature of the rules engine. That said, there isn't any validation done by the Rules service to reject a configuration with a FIFO topic. I ran a test with a FIFO queue as the target and received an error in the IoT logs Invalid parameter: The MessageGroupId parameter is required for FIFO topics (Service: AmazonSNS; Status Code: 400; Error Code: InvalidParameter;. So I'll argue that throwing this error is likely to save at least a couple people from headache and wasted time debugging a bad configuration.

Copy link
Contributor Author

@AdamVD AdamVD Feb 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also worth noting that the current SQS action implementation does not perform this check, even though the same FIFO limitation applies. If we decide this check is justified we might want to create a ticket to apply the same in the SQS action.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm amazed at your great insight! And I think it's good:+1:.
Reference for reviewer: the API reference about .fifo sufix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll second @yamatatsu's comment, this is some excellent detective work @AdamVD! I agree with everything you said.

One subtlety here. Note that topicName could be something that CDK calls a Token. For example, in your integ tests, notice that tokenName resolves to { Ref: 'MyTopic86869434' }.

Because of that, it would be a good idea to call the Token.isUnresolved() method, and only do the check if that method returns false (otherwise, we're comparing against the encoded Token, which doesn't make too much sense).

Also, if we need ITopic to have a fifo property, like IQueue does - let's do it! If this is a small change in the SNS module, I'm fine adding it to this PR, otherwise, let's create an issue in the backlog for it, and follow-up after this PR gets merged.

Let me know what you think @AdamVD!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @skinny85 this is a great call out, the Token concept is new new me and I don't yet understand exactly when it is present. However, I wonder if #18740 makes it so that the name of a FIFO topic is always resolved. Regardless, this implementation shouldn't be dependent on a temporary shortcoming of another module, so I'll look into expanding ITopic with a fifo property as a more robust solution.

this.messageFormat = props.messageFormat;
}

bind(rule: iot.ITopicRule): iot.ActionConfig {
Copy link
Contributor Author

@AdamVD AdamVD Feb 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Limitation here is we don't check if the SNS topic is encrypted and adjust the role or KMS resource policy accordingly. ITopic doesn't expose encryption details so I'm not sure how we'd perform this check (unlike IQueue.encryptionMasterKey). The current SQS queue action has the same limitation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we used the grantPublish() method of ITopic, instead of creating the Policy directly?

If that method doesn't handle permissions to the Key correctly, then that's a bug in the SNS library!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I should use the grantPublish() method. Unfortunately, it looks like this is another limitation of the current SNS topic implementation. The grant method doesn't take encryption into account.

From topic-base.ts:

  /**
   * Grant topic publishing permissions to the given identity
   */
  public grantPublish(grantee: iam.IGrantable) {
    return iam.Grant.addToPrincipalOrResource({
      grantee,
      actions: ['sns:Publish'],
      resourceArns: [this.topicArn],
      resource: this,
    });
  }

Compare this against queue-base.ts:

  public grantSendMessages(grantee: iam.IGrantable) {
    const ret = this.grant(grantee,
      'sqs:SendMessage',
      'sqs:GetQueueAttributes',
      'sqs:GetQueueUrl');

    if (this.encryptionMasterKey) {
      // kms:Decrypt necessary to execute grantsendMessages to an SSE enabled SQS queue
      this.encryptionMasterKey.grantEncryptDecrypt(grantee);
    }
    return ret;
  }

This is concisely described in #18387, and can be assumed to affect all other SNS topic integrations (e.g. #16271, #11121) unless a separate workaround was implemented. I'd be happy to look into this and and try to bring it in-line with what SQS queues do, but that probably shouldn't be a part of this PR. What do you think @skinny85?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that said, changing this implementation to use grantPublish() means that no further changes will be required once #18387 is fixed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with everything you said 🙂. Ideally, this would be a separate PR that only touches the SNS module, but if you'd rather do it all in one, because that will be easier for you - go for it. You're fixing bugs in our project, I won't be too harsh 😉.

Copy link
Contributor

@yamatatsu yamatatsu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is so grad for me to see more committers!:tada:
I reviewed with perspective I've taked comments by maintainer usually. It is aimed to reduce suggestions the maintainer will do.

* @param props Properties to configure the action.
*/
constructor(topic: sns.ITopic, props: SnsTopicActionProps = {}) {
if (topic.topicName.endsWith('.fifo')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm amazed at your great insight! And I think it's good:+1:.
Reference for reviewer: the API reference about .fifo sufix.

Comment on lines 11 to 18
/**
* RAW message format.
*/
RAW = 'RAW',
/**
* JSON message format.
*/
JSON = 'JSON'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty lines between definitions:

Suggested change
/**
* RAW message format.
*/
RAW = 'RAW',
/**
* JSON message format.
*/
JSON = 'JSON'
/**
* RAW message format.
*/
RAW = 'RAW',
/**
* JSON message format.
*/
JSON = 'JSON'

import * as cdk from '@aws-cdk/core';
import * as actions from '../../lib';

const app = new cdk.App();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is better above the line it uses app:

const app = new cdk.App();
new TestStack(app, 'test-stack');

}
}

new TestStack(app, 'test-stack');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use more clear name:

Suggested change
new TestStack(app, 'test-stack');
new TestStack(app, 'sns-topic-action-test-stack');

Comment on lines 30 to 42
Actions: [
{
Sns: {
RoleArn: {
'Fn::GetAtt': [
RULE_ROLE_ID,
'Arn',
],
},
TargetArn: SNS_TOPIC_ARN,
},
},
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less indentions:

Suggested change
Actions: [
{
Sns: {
RoleArn: {
'Fn::GetAtt': [
RULE_ROLE_ID,
'Arn',
],
},
TargetArn: SNS_TOPIC_ARN,
},
},
],
Actions: [{
Sns: {
RoleArn: { 'Fn::GetAtt': [RULE_ROLE_ID, 'Arn'] },
TargetArn: SNS_TOPIC_ARN,
},
}],

Comment on lines 48 to 56
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'iot.amazonaws.com',
},
},
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less indentions:

Suggested change
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'iot.amazonaws.com',
},
},
],
Statement: [{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: { Service: 'iot.amazonaws.com' },
}],

Comment on lines 62 to 68
Statement: [
{
Action: 'sns:Publish',
Effect: 'Allow',
Resource: SNS_TOPIC_ARN,
},
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less indentions:

Suggested change
Statement: [
{
Action: 'sns:Publish',
Effect: 'Allow',
Resource: SNS_TOPIC_ARN,
},
],
Statement: [{
Action: 'sns:Publish',
Effect: 'Allow',
Resource: SNS_TOPIC_ARN,
}],

Comment on lines 70 to 72
Roles: [
{ Ref: RULE_ROLE_ID },
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less indentions:

Suggested change
Roles: [
{ Ref: RULE_ROLE_ID },
],
Roles: [{ Ref: RULE_ROLE_ID }],

@AdamVD
Copy link
Contributor Author

AdamVD commented Feb 6, 2022

Thanks for the warm welcome @yamatatsu and the suggestions! Those formatting changes really improve the readability of the tests.

Copy link
Contributor

@skinny85 skinny85 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welcome to the CDK contributors @AdamVD! We're very lucky to have you, as your first contribution is of very high quality already 🙂.

I have a few comments, but they're all very minor.

* Configuration options for the SNS topic action.
*/
export interface SnsTopicActionProps extends CommonActionProps {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this empty line here:

Suggested change

* @param props Properties to configure the action.
*/
constructor(topic: sns.ITopic, props: SnsTopicActionProps = {}) {
if (topic.topicName.endsWith('.fifo')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll second @yamatatsu's comment, this is some excellent detective work @AdamVD! I agree with everything you said.

One subtlety here. Note that topicName could be something that CDK calls a Token. For example, in your integ tests, notice that tokenName resolves to { Ref: 'MyTopic86869434' }.

Because of that, it would be a good idea to call the Token.isUnresolved() method, and only do the check if that method returns false (otherwise, we're comparing against the encoded Token, which doesn't make too much sense).

Also, if we need ITopic to have a fifo property, like IQueue does - let's do it! If this is a small change in the SNS module, I'm fine adding it to this PR, otherwise, let's create an issue in the backlog for it, and follow-up after this PR gets merged.

Let me know what you think @AdamVD!

*/
constructor(topic: sns.ITopic, props: SnsTopicActionProps = {}) {
if (topic.topicName.endsWith('.fifo')) {
throw Error('An SNS topic IoT Rule action cannot be configured with a FIFO topic, only standard topics are allowed.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can word-smith this error message a little bit:

Suggested change
throw Error('An SNS topic IoT Rule action cannot be configured with a FIFO topic, only standard topics are allowed.');
throw Error('IoT Rule actions cannot be used with FIFO SNS Topics, please pass a non-FIFO Topic instead');

this.messageFormat = props.messageFormat;
}

bind(rule: iot.ITopicRule): iot.ActionConfig {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we used the grantPublish() method of ITopic, instead of creating the Policy directly?

If that method doesn't handle permissions to the Key correctly, then that's a bug in the SNS library!

import * as actions from '../../lib';

const SNS_TOPIC_ARN = 'arn:aws:sns::123456789012:test-topic';
const RULE_ROLE_ID = 'MyTopicRuleTopicRuleActionRoleCE2D05DA';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably inline this variable (it's exact value depends on the specifics of the test it's used in, for example, it won't be generated in the explicit Role test).

@mergify mergify bot dismissed skinny85’s stale review February 8, 2022 01:45

Pull request has been modified.

@AdamVD
Copy link
Contributor Author

AdamVD commented Feb 8, 2022

@skinny85 thanks for the kind words and the review! I've addressed the trivial changes and will begin looking into exposing fifo from ITopic so that this implementation is more robust.

@skinny85
Copy link
Contributor

skinny85 commented Feb 8, 2022

@skinny85 thanks for the kind words and the review! I've addressed the trivial changes and will begin looking into exposing fifo from ITopic so that this implementation is more robust.

My pleasure 🙂. Thank you for the contribution! Let me know when you're ready for the next round of reviews - just re-request my review (there's a button in the top-right of the PR window, next to my avatar).

@AdamVD AdamVD requested a review from skinny85 February 9, 2022 02:09
Copy link
Contributor

@skinny85 skinny85 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fantastic, thanks so much for the contribution @AdamVD! I hope it's only the first one of many more to come 🙂.

@mergify
Copy link
Contributor

mergify bot commented Feb 9, 2022

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject89A8053A-LhjRyN9kxr8o
  • Commit ID: 061b9bd
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify mergify bot merged commit 3a39f6b into aws:master Feb 9, 2022
@mergify
Copy link
Contributor

mergify bot commented Feb 9, 2022

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@AdamVD AdamVD deleted the feature/iot-rule-sns-action branch February 9, 2022 23:57
moelasmar pushed a commit to moelasmar/aws-cdk that referenced this pull request Feb 15, 2022
First-time contributor 👋 

fixes aws#17700 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
TikiTDO pushed a commit to TikiTDO/aws-cdk that referenced this pull request Feb 21, 2022
First-time contributor 👋 

fixes aws#17700 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

(iot): TopicRule action for SNS in AWS IoT
4 participants