Skip to content

Commit

Permalink
Add support for step functions in alarm generation (#694)
Browse files Browse the repository at this point in the history
  • Loading branch information
rleighton authored Apr 13, 2020
1 parent dcf7b72 commit 7a87e60
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 10 deletions.
38 changes: 28 additions & 10 deletions tools/cfngen/cloudwatchcf/alarms.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,42 @@ type AlarmProperties struct {
type MetricDimension struct {
Name string

// Use only one of Value or ValueRef
// Use only one of Value, ValueSub or ValueRef
Value string

valueRef *RefString
valueSub *SubString
}

func (m *MetricDimension) MarshalJSON() ([]byte, error) {
if m.valueRef == nil {
if m.valueRef == nil && m.valueSub == nil {
// Most common case - the struct can be marshaled like normal (json ignores nil valueRef)
return jsoniter.Marshal(*m) // dereference to avoid infinite recursion
}

// Otherwise, marshal a new struct where "Value" is actually a struct with the nested ref
return jsoniter.Marshal(&struct {
Name string
Value RefString
}{
Name: m.Name,
Value: *m.valueRef,
})
// marshal a new struct where "Value" is actually a struct with the nested ref
if m.valueRef != nil && m.valueSub == nil {
return jsoniter.Marshal(&struct {
Name string
Value RefString
}{
Name: m.Name,
Value: *m.valueRef,
})
}

// marshal a new struct where "Value" is actually a struct with the nested sub
if m.valueRef == nil && m.valueSub != nil {
return jsoniter.Marshal(&struct {
Name string
Value SubString
}{
Name: m.Name,
Value: *m.valueSub,
})
}

panic("valueRef and valueSub cannot both be set")
}

type RefString struct {
Expand Down Expand Up @@ -242,6 +258,8 @@ func alarmDispatchOnType(resourceType string, resource map[interface{}]interface
return generateDynamoDBAlarms(resource)
case "AWS::Serverless::Function":
return generateLambdaAlarms(resource, settings)
case "AWS::StepFunctions::StateMachine":
return generateSFNAlarms(resource)
}
return alarms
}
57 changes: 57 additions & 0 deletions tools/cfngen/cloudwatchcf/alarms_sfn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cloudwatchcf

/**
* Panther is a Cloud-Native SIEM for the Modern Security Team.
* Copyright (C) 2020 Panther Labs Inc
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import "fmt"

type SFNAlarm struct {
Alarm
}

func NewSFNAlarm(alarmType, metricName, message string, resource map[interface{}]interface{}) *SFNAlarm {
const (
metricDimension = "StateMachineArn"
metricNamespace = "AWS/States"
)
stateMachineName := getResourceProperty("StateMachineName", resource)
stateMachineArn := fmt.Sprintf("arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:%s",
stateMachineName)
alarmName := AlarmName(alarmType, stateMachineName)
alarm := &SFNAlarm{
Alarm: *NewAlarm(stateMachineName, alarmName,
fmt.Sprintf("State machine %s %s. See: %s#%s", stateMachineName, message, documentationURL, stateMachineName)),
}
alarm.Alarm.Metric(metricNamespace, metricName, []MetricDimension{{
Name: metricDimension,
valueSub: &SubString{Sub: stateMachineArn},
},
})
return alarm
}

func generateSFNAlarms(resource map[interface{}]interface{}) []*Alarm {
return []*Alarm{
NewSFNAlarm(
"SFNError",
"ExecutionsFailed",
"is failing",
resource,
).SumCountThreshold(0, 60*5),
}
}
20 changes: 20 additions & 0 deletions tools/cfngen/cloudwatchcf/testdata/cf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,23 @@ Resources:
- kms:Encrypt
- kms:GenerateDataKey
Resource: !Sub arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${SQSKeyId}

MyStateMachine:
Type: 'AWS::StepFunctions::StateMachine'
Properties:
StateMachineName: my-state-machine
DefinitionString: !Sub
- |-
{
"Comment": "A Hello World example using an AWS Lambda function",
"StartAt": "HelloWorld",
"States": {
"HelloWorld": {
"Type": "Task",
"Resource": "${lambdaArn}",
"End": true
}
}
}
- { lambdaArn: !GetAtt [MyLambdaFunction, Arn] }
RoleArn: !GetAtt [StatesExecutionRole, Arn]
29 changes: 29 additions & 0 deletions tools/cfngen/cloudwatchcf/testdata/generated_test_alarms.json
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,35 @@
"Statistic": "Sum"
}
},
"PantherAlarmSFNErrormystatemachine": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmName": "PantherAlarm-SFNError-my-state-machine",
"AlarmDescription": "State machine my-state-machine is failing. See: https://docs.runpanther.io/operations/runbooks#my-state-machine",
"AlarmActions": [
{
"Ref": "AlarmTopicArn"
}
],
"TreatMissingData": "notBreaching",
"Namespace": "AWS/States",
"MetricName": "ExecutionsFailed",
"Dimensions": [
{
"Name": "StateMachineArn",
"Value": {
"Fn::Sub": "arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:my-state-machine"
}
}
],
"ComparisonOperator": "GreaterThanThreshold",
"EvaluationPeriods": 1,
"Period": 300,
"Threshold": 0,
"Unit": "Count",
"Statistic": "Sum"
}
},
"PantherAlarmSNSErrortestnotifications": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
Expand Down

0 comments on commit 7a87e60

Please sign in to comment.