-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Conversation
* @param props Properties to configure the action. | ||
*/ | ||
constructor(topic: sns.ITopic, props: SnsTopicActionProps = {}) { | ||
if (topic.topicName.endsWith('.fifo')) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 😉.
There was a problem hiding this 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')) { |
There was a problem hiding this comment.
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.
/** | ||
* RAW message format. | ||
*/ | ||
RAW = 'RAW', | ||
/** | ||
* JSON message format. | ||
*/ | ||
JSON = 'JSON' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty lines between definitions:
/** | |
* 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(); |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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:
new TestStack(app, 'test-stack'); | |
new TestStack(app, 'sns-topic-action-test-stack'); |
Actions: [ | ||
{ | ||
Sns: { | ||
RoleArn: { | ||
'Fn::GetAtt': [ | ||
RULE_ROLE_ID, | ||
'Arn', | ||
], | ||
}, | ||
TargetArn: SNS_TOPIC_ARN, | ||
}, | ||
}, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less indentions:
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, | |
}, | |
}], |
Statement: [ | ||
{ | ||
Action: 'sts:AssumeRole', | ||
Effect: 'Allow', | ||
Principal: { | ||
Service: 'iot.amazonaws.com', | ||
}, | ||
}, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less indentions:
Statement: [ | |
{ | |
Action: 'sts:AssumeRole', | |
Effect: 'Allow', | |
Principal: { | |
Service: 'iot.amazonaws.com', | |
}, | |
}, | |
], | |
Statement: [{ | |
Action: 'sts:AssumeRole', | |
Effect: 'Allow', | |
Principal: { Service: 'iot.amazonaws.com' }, | |
}], |
Statement: [ | ||
{ | ||
Action: 'sns:Publish', | ||
Effect: 'Allow', | ||
Resource: SNS_TOPIC_ARN, | ||
}, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less indentions:
Statement: [ | |
{ | |
Action: 'sns:Publish', | |
Effect: 'Allow', | |
Resource: SNS_TOPIC_ARN, | |
}, | |
], | |
Statement: [{ | |
Action: 'sns:Publish', | |
Effect: 'Allow', | |
Resource: SNS_TOPIC_ARN, | |
}], |
Roles: [ | ||
{ Ref: RULE_ROLE_ID }, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less indentions:
Roles: [ | |
{ Ref: RULE_ROLE_ID }, | |
], | |
Roles: [{ Ref: RULE_ROLE_ID }], |
Thanks for the warm welcome @yamatatsu and the suggestions! Those formatting changes really improve the readability of the tests. |
There was a problem hiding this 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 { | ||
|
There was a problem hiding this comment.
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:
* @param props Properties to configure the action. | ||
*/ | ||
constructor(topic: sns.ITopic, props: SnsTopicActionProps = {}) { | ||
if (topic.topicName.endsWith('.fifo')) { |
There was a problem hiding this comment.
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.'); |
There was a problem hiding this comment.
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:
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 { |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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).
Pull request has been modified.
@skinny85 thanks for the kind words and the review! I've addressed the trivial changes and will begin looking into exposing |
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). |
There was a problem hiding this 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 🙂.
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 CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
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). |
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*
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*
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