Skip to content

Commit c0d6242

Browse files
authored
Add files via upload
1 parent 13a1934 commit c0d6242

File tree

1 file changed

+204
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)