diff --git a/config/notifiers.go b/config/notifiers.go index 2797843b..b9810804 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -133,7 +133,6 @@ var ( NotifierConfig: NotifierConfig{ VSendResolved: true, }, - APIVersion: "sns.default.api_version", Subject: `{{ template "sns.default.subject" . }}`, Message: `{{ template "sns.default.message" . }}`, } @@ -608,7 +607,6 @@ type SNSConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` APIUrl string `yaml:"api_url" json:"api_url"` - APIVersion string `yaml:"api_version,omitempty" json:"api_version,omitempty"` Sigv4 SigV4Config `yaml:"sigv4" json:"sigv4"` TopicARN string `yaml:"topic_arn,omitempty" json:"topic_arn,omitempty"` PhoneNumber string `yaml:"phone_number,omitempty" json:"phone_number,omitempty"` @@ -625,7 +623,7 @@ func (c *SNSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal((*plain)(c)); err != nil { return err } - if c.TargetARN == "" && c.TopicARN == "" && c.PhoneNumber == "" { + if (c.TargetARN == "") != (c.TopicARN == "") != (c.PhoneNumber == "") { return fmt.Errorf("must provide either a Target ARN, Topic ARN, or Phone Number for SNS config") } if (c.Sigv4.AccessKey == "") != (c.Sigv4.SecretKey == "") { diff --git a/docs/configuration.md b/docs/configuration.md index 0953febc..7fa1f5a4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -699,9 +699,6 @@ value: # The SNS API URL i.e. https://sns.us-east-2.amazonaws.com [api_url: ] -# The SNS API version i.e. -[ api_version: | default = sns.default.api_version ] - # Configures AWS's Signature Verification 4 signing process to sign requests. sigv4: [ ] diff --git a/notify/sns/sns.go b/notify/sns/sns.go index 151e62a5..77d4a7ae 100644 --- a/notify/sns/sns.go +++ b/notify/sns/sns.go @@ -27,6 +27,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sns" "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" @@ -70,6 +71,14 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err creds = credentials.NewStaticCredentials(n.conf.Sigv4.AccessKey, string(n.conf.Sigv4.SecretKey), "") } + attributes := make(map[string]*sns.MessageAttributeValue, len(n.conf.Attributes)) + if len(n.conf.Attributes) > 0 { + for k, v := range n.conf.Attributes { + attributes[tmpl(k)] = &sns.MessageAttributeValue{DataType: aws.String("String"), StringValue: aws.String(tmpl(v))} + } + + } + sess, err := session.NewSessionWithOptions(session.Options{ Config: aws.Config{ Region: aws.String(n.conf.Sigv4.Region), @@ -87,18 +96,13 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err sess.Config.Credentials = stscreds.NewCredentials(sess, n.conf.Sigv4.RoleARN) } + // Max message size for a message in a SNS publish request is 256KB, except for SMS messages where the limit is 1600 characters/runes. + messageSize := 256 * 1024 client := sns.New(sess, &aws.Config{Credentials: creds}) publishInput := &sns.PublishInput{} if n.conf.TopicARN != "" { publishInput.SetTopicArn(tmpl(n.conf.TopicARN)) - messageToSend, isTrunc, err := validateAndTruncateMessage(tmpl(n.conf.Message)) - if err != nil { - return false, err - } - if isTrunc { - n.conf.Attributes["truncated"] = "true" - } if n.isFifo == nil { checkFifo, err := checkTopicFifoAttribute(client, n.conf.TopicARN) @@ -120,43 +124,32 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err publishInput.SetMessageDeduplicationId(key.Hash()) publishInput.SetMessageGroupId(key.Hash()) } - - publishInput.SetMessage(messageToSend) } if n.conf.PhoneNumber != "" { publishInput.SetPhoneNumber(tmpl(n.conf.PhoneNumber)) - // If SMS message is over 1600 chars, SNS will reject the message. - _, isTruncated := notify.Truncate(tmpl(n.conf.Message), 1600) - if isTruncated { - return false, fmt.Errorf("SMS message exeeds length of 1600 charactors") - } else { - publishInput.SetMessage(tmpl(n.conf.Message)) - } + // If we have an SMS message, we need to truncate to 1600 characters/runes. + messageSize = 1600 } if n.conf.TargetARN != "" { publishInput.SetTargetArn(tmpl(n.conf.TargetARN)) - messageToSend, isTrunc, err := validateAndTruncateMessage(tmpl(n.conf.Message)) - if err != nil { - return false, err - } - if isTrunc { - n.conf.Attributes["truncated"] = "true" - } - publishInput.SetMessage(messageToSend) + } - if len(n.conf.Attributes) > 0 { - attributes := map[string]*sns.MessageAttributeValue{} - for k, v := range n.conf.Attributes { - attributes[tmpl(k)] = &sns.MessageAttributeValue{DataType: aws.String("String"), StringValue: aws.String(tmpl(v))} - } - publishInput.SetMessageAttributes(attributes) + messageToSend, isTrunc, err := validateAndTruncateMessage(tmpl(n.conf.Message), messageSize) + if err != nil { + return false, err } + if isTrunc { + attributes[tmpl("truncated")] = &sns.MessageAttributeValue{DataType: aws.String("String"), StringValue: aws.String(tmpl("true"))} + } + publishInput.SetMessage(messageToSend) if n.conf.Subject != "" { publishInput.SetSubject(tmpl(n.conf.Subject)) } + publishInput.SetMessageAttributes(attributes) + publishOutput, err := client.Publish(publishInput) if err != nil { if e, ok := err.(awserr.RequestFailure); ok { @@ -166,10 +159,7 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err } } - err = n.logger.Log(publishOutput.String()) - if err != nil { - return false, err - } + level.Debug(n.logger).Log("msg", "SNS publish successfully sent", "message_id", publishOutput.MessageId, "sequence number", publishOutput.SequenceNumber) return false, nil } @@ -180,21 +170,21 @@ func checkTopicFifoAttribute(client *sns.SNS, topicARN string) (bool, error) { return false, err } ta := topicAttributes.Attributes["FifoTopic"] - if ta != nil && *ta == "true" { + if aws.StringValue(ta) == "true" { return true, nil } return false, nil } -func validateAndTruncateMessage(message string) (string, bool, error) { - if utf8.ValidString(message) { - // if the message is larger than 256KB we have to truncate. - if len(message) > 256*1024 { - truncated := make([]byte, 256*1024) - copy(truncated, message) - return string(truncated), true, nil - } +func validateAndTruncateMessage(message string, sizeInBytes int) (string, bool, error) { + if !utf8.ValidString(message) { + return "", false, fmt.Errorf("non utf8 encoded message string") + } + if len(message) <= sizeInBytes { return message, false, nil } - return "", false, fmt.Errorf("non utf8 encoded message string") + // if the message is larger than our specified size we have to truncate. + truncated := make([]byte, sizeInBytes) + copy(truncated, message) + return string(truncated), true, nil } diff --git a/notify/sns/sns_test.go b/notify/sns/sns_test.go index 9e36e5a4..7d300ab1 100644 --- a/notify/sns/sns_test.go +++ b/notify/sns/sns_test.go @@ -24,7 +24,7 @@ func TestValidateAndTruncateMessage(t *testing.T) { for i := range sBuff { sBuff[i] = byte(33) } - truncatedMessage, isTruncated, err := validateAndTruncateMessage(string(sBuff)) + truncatedMessage, isTruncated, err := validateAndTruncateMessage(string(sBuff), 256*1024) require.True(t, isTruncated) require.NoError(t, err) require.NotEqual(t, sBuff, truncatedMessage) @@ -34,12 +34,12 @@ func TestValidateAndTruncateMessage(t *testing.T) { for i := range sBuff { sBuff[i] = byte(33) } - truncatedMessage, isTruncated, err = validateAndTruncateMessage(string(sBuff)) + truncatedMessage, isTruncated, err = validateAndTruncateMessage(string(sBuff), 100) require.False(t, isTruncated) require.NoError(t, err) require.Equal(t, string(sBuff), truncatedMessage) invalidUtf8String := "\xc3\x28" - _, _, err = validateAndTruncateMessage(invalidUtf8String) + _, _, err = validateAndTruncateMessage(invalidUtf8String, 100) require.Error(t, err) }