-
Notifications
You must be signed in to change notification settings - Fork 134
/
Copy pathguarddutyeventprocessor.yaml
204 lines (190 loc) · 8.2 KB
/
guarddutyeventprocessor.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
AWSTemplateFormatVersion: 2010-09-09
Description: >
This function is invoked by AWS CloudWatch events in response to state change
in your AWS resources which matches a event target definition. The event
payload received is then forwarded to Sumo Logic HTTP source endpoint.
Parameters:
SumoEndpointUrl:
Type: String
Outputs:
CloudWatchEventFunction:
Description: CloudWatchEvent Processor Function ARN
Value: !GetAtt
- CloudWatchEventFunction
- Arn
Resources:
CloudWatchEventFunction:
Type: 'AWS::Lambda::Function'
Metadata:
SamResourceId: CloudWatchEventFunction
Properties:
Code:
ZipFile: |
// SumoLogic Endpoint to post logs
var SumoURL = process.env.SUMO_ENDPOINT;
// For some beta AWS services, the default is to remove the outer fields of the received object since they are not useful.
// change this if necessary.
var removeOuterFields = false;
// The following parameters override the sourceCategoryOverride, sourceHostOverride and sourceNameOverride metadata fields within SumoLogic.
// Not these can also be overridden via json within the message payload. See the README for more information.
var sourceCategoryOverride = process.env.SOURCE_CATEGORY_OVERRIDE || ''; // If empty sourceCategoryOverride will not be overridden
var sourceHostOverride = process.env.SOURCE_HOST_OVERRIDE || ''; // If empty sourceHostOverride will not be set to the name of the logGroup
var sourceNameOverride = process.env.SOURCE_NAME_OVERRIDE || ''; // If empty sourceNameOverride will not be set to the name of the logStream
var retryInterval = process.env.RETRY_INTERVAL || 5000; // the interval in millisecs between retries
var numOfRetries = process.env.NUMBER_OF_RETRIES || 3; // the number of retries
var https = require('https');
var zlib = require('zlib');
var url = require('url');
Promise.retryMax = function(fn,retry,interval,fnParams) {
return fn.apply(this,fnParams).catch( err => {
var waitTime = typeof interval === 'function' ? interval() : interval;
console.log("Retries left " + (retry-1) + " delay(in ms) " + waitTime);
return (retry>1? Promise.wait(waitTime).then(()=> Promise.retryMax(fn,retry-1,interval, fnParams)):Promise.reject(err));
});
}
Promise.wait = function(delay) {
return new Promise((fulfill,reject)=> {
//console.log(Date.now());
setTimeout(fulfill,delay||0);
});
};
function exponentialBackoff(seed) {
var count = 0;
return function() {
count++;
return count*seed;
}
}
function postToSumo(callback, messages) {
var messagesTotal = Object.keys(messages).length;
var messagesSent = 0;
var messageErrors = [];
var urlObject = url.parse(SumoURL);
var options = {
'hostname': urlObject.hostname,
'path': urlObject.pathname,
'method': 'POST'
};
var finalizeContext = function () {
var total = messagesSent + messageErrors.length;
if (total == messagesTotal) {
console.log('messagesSent: ' + messagesSent + ' messagesErrors: ' + messageErrors.length);
if (messageErrors.length > 0) {
callback('errors: ' + messageErrors);
} else {
callback(null, "Success");
}
}
};
function httpSend(options, headers, data) {
return new Promise( (resolve,reject) => {
var curOptions = options;
curOptions.headers = headers;
var req = https.request(curOptions, function (res) {
var body = '';
res.setEncoding('utf8');
res.on('data', function (chunk) {
body += chunk; // don't really do anything with body
});
res.on('end', function () {
if (res.statusCode == 200) {
resolve(body);
} else {
reject({'error':'HTTP Return code ' + res.statusCode,'res':res});
}
});
});
req.on('error', function (e) {
reject({'error':e,'res':null});
});
for (var i = 0; i < data.length; i++) {
req.write(JSON.stringify(data[i]) + '\n');
}
console.log("sending to Sumo...")
req.end();
});
}
Object.keys(messages).forEach(function (key, index) {
var headerArray = key.split(':');
var headers = {
'X-Sumo-Name': headerArray[0],
'X-Sumo-Category': headerArray[1],
'X-Sumo-Host': headerArray[2],
'X-Sumo-Client': 'cloudwatchevents-aws-lambda'
};
Promise.retryMax(httpSend, numOfRetries, retryInterval, [options, headers, messages[key]]).then((body)=> {
messagesSent++;
finalizeContext()
}).catch((e) => {
messageErrors.push(e.error);
finalizeContext();
});
});
}
exports.handler = function (event, context, callback) {
// Used to hold chunks of messages to post to SumoLogic
var messageList = {};
var final_event;
// Validate URL has been set
var urlObject = url.parse(SumoURL);
if (urlObject.protocol != 'https:' || urlObject.host === null || urlObject.path === null) {
callback('Invalid SUMO_ENDPOINT environment variable: ' + SumoURL);
}
//console.log(event);
if ((event.source==="aws.guardduty") || (removeOuterFields)) {
final_event =event.detail;
} else {
final_event = event;
}
messageList[sourceNameOverride+':'+sourceCategoryOverride+':'+sourceHostOverride]=[final_event];
postToSumo(callback, messageList);
};
Handler: index.handler
Role: !GetAtt
- CloudWatchEventFunctionRole
- Arn
Runtime: nodejs22.x
Timeout: 300
Environment:
Variables:
SUMO_ENDPOINT: !Ref SumoEndpointUrl
Tags:
- Key: 'lambda:createdBy'
Value: SAM
CloudWatchEventFunctionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Tags:
- Key: 'lambda:createdBy'
Value: SAM
CloudWatchEventFunctionCloudWatchEventTrigger:
Type: 'AWS::Events::Rule'
Properties:
EventPattern:
source:
- aws.guardduty
Targets:
- Arn: !GetAtt
- CloudWatchEventFunction
- Arn
Id: CloudWatchEventFunctionCloudWatchEventTriggerLambdaTarget
CloudWatchEventFunctionCloudWatchEventTriggerPermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !Ref CloudWatchEventFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt
- CloudWatchEventFunctionCloudWatchEventTrigger
- Arn