Skip to content

Commit 3d86c5d

Browse files
authored
Merge pull request #2056 from AkhtarAmir/M/Aws/Api-gateway-v2-access-logging
M/aws/api gateway v2 access logging
2 parents 5ad172e + 0c5b98a commit 3d86c5d

File tree

4 files changed

+310
-0
lines changed

4 files changed

+310
-0
lines changed

exports.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = {
2626
'apigatewayDefaultEndpointDisabled' : require(__dirname + '/plugins/aws/apigateway/apigatewayDefaultEndpointDisabled.js'),
2727
'apigatewayAuthorization' : require(__dirname + '/plugins/aws/apigateway/apigatewayAuthorization.js'),
2828
'apigatewayV2Authorization' : require(__dirname + '/plugins/aws/apigateway/apigatewayV2Authorization.js'),
29+
'apigatewayV2AccessLogging' : require(__dirname + '/plugins/aws/apigateway/apigatewayV2AccessLogging.js'),
2930
'apigatewayRequestValidation' : require(__dirname + '/plugins/aws/apigateway/apigatewayRequestValidation.js'),
3031

3132
'restrictExternalTraffic' : require(__dirname + '/plugins/aws/appmesh/restrictExternalTraffic.js'),

helpers/aws/api.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,12 @@ var postcalls = [
18601860
}
18611861
},
18621862
ApiGatewayV2: {
1863+
getStages: {
1864+
reliesOnService: 'apigatewayv2',
1865+
reliesOnCall: 'getApis',
1866+
filterKey: 'ApiId',
1867+
filterValue: 'ApiId'
1868+
},
18631869
getAuthorizers: {
18641870
reliesOnService: 'apigatewayv2',
18651871
reliesOnCall: 'getApis',
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
var async = require('async');
2+
var helpers = require('../../../helpers/aws');
3+
4+
module.exports = {
5+
title: 'API Gateway V2 Access Logging',
6+
category: 'API Gateway',
7+
domain: 'Availability',
8+
severity: 'Medium',
9+
description: 'Ensures that Amazon API Gateway V2 API stages have access logging enabled.',
10+
more_info: 'API Gateway V2 access logs provide detailed information about APIs and how the caller accessed the API. These logs are useful for security and access audits which helps to analyze traffic patterns and troubleshoot issues.',
11+
recommended_action: 'Modify API Gateway V2 configuration and ensure that access logging is configured for each stage.',
12+
link: 'https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging.html',
13+
apis: ['ApiGatewayV2:getApis','ApiGatewayV2:getStages'],
14+
realtime_triggers: ['ApiGatewayV2:createApi','ApiGatewayV2:deleteApi','ApiGatewayV2:importApi','ApiGatewayv2:CreateStage','ApiGatewayv2:UpdateStage','ApiGatewayv2:DeleteStage'],
15+
16+
run: function(cache, settings, callback) {
17+
var results = [];
18+
var source = {};
19+
var regions = helpers.regions(settings);
20+
var awsOrGov = helpers.defaultPartition(settings);
21+
22+
async.each(regions.apigatewayv2, function(region, rcb){
23+
var getApis = helpers.addSource(cache, source,
24+
['apigatewayv2', 'getApis', region]);
25+
26+
if (!getApis) return rcb();
27+
28+
if (getApis.err || !getApis.data) {
29+
helpers.addResult(results, 3,
30+
`Unable to query for API Gateway V2 APIs: ${helpers.addError(getApis)}`, region);
31+
return rcb();
32+
}
33+
34+
if (!getApis.data.length) {
35+
helpers.addResult(results, 0, 'No API Gateway V2 APIs found', region);
36+
return rcb();
37+
}
38+
39+
getApis.data.forEach(api => {
40+
if (!api.ApiId) return;
41+
42+
var apiArn = `arn:${awsOrGov}:apigateway:${region}::/apis/${api.ApiId}`;
43+
44+
var getStages = helpers.addSource(cache, source,
45+
['apigatewayv2', 'getStages', region, api.ApiId]);
46+
47+
if (!getStages || getStages.err || !getStages.data || !getStages.data.Items) {
48+
helpers.addResult(results, 3,
49+
`Unable to query for API Gateway V2 API Stages: ${helpers.addError(getStages)}`,
50+
region, apiArn);
51+
return;
52+
}
53+
54+
if (!getStages.data.Items.length) {
55+
helpers.addResult(results, 0,
56+
'No API Gateway V2 API Stages found',
57+
region, apiArn);
58+
return;
59+
}
60+
61+
getStages.data.Items.forEach(stage => {
62+
if (!stage.StageName) return;
63+
64+
var stageArn = `arn:${awsOrGov}:apigateway:${region}::/apis/${api.ApiId}/stages/${stage.StageName}`;
65+
if (stage.AccessLogSetting) {
66+
helpers.addResult(results, 0,
67+
'API Gateway V2 API stage has access logging enabled',
68+
region, stageArn);
69+
} else {
70+
helpers.addResult(results, 2,
71+
'API Gateway V2 API stage does not have access logging enabled',
72+
region, stageArn);
73+
}
74+
});
75+
76+
});
77+
78+
rcb();
79+
}, function() {
80+
callback(null, results, source);
81+
});
82+
}
83+
};
84+
85+
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
var expect = require('chai').expect;
2+
var apigatewayV2AccessLogging = require('./apigatewayV2AccessLogging');
3+
4+
const createCache = (getApis, getStages) => {
5+
if (getApis && getApis.length && getApis[0].ApiId) var restApiId = getApis[0].ApiId;
6+
return {
7+
apigatewayv2: {
8+
getApis: {
9+
'us-east-1': {
10+
data: getApis
11+
}
12+
},
13+
getStages: {
14+
'us-east-1': {
15+
[restApiId]: {
16+
data: {
17+
Items: getStages
18+
}
19+
}
20+
}
21+
}
22+
}
23+
};
24+
};
25+
26+
const createErrorCache = () => {
27+
return {
28+
apigatewayv2: {
29+
getApis: {
30+
'us-east-1': {
31+
err: {
32+
message: 'error fetching API Gateway v2 APIs'
33+
},
34+
},
35+
},
36+
getStages: {
37+
'us-east-1': {
38+
err: {
39+
message: 'error fetching API Gateway v2 stages'
40+
},
41+
},
42+
}
43+
44+
},
45+
};
46+
};
47+
48+
const createUnknownForStage = (api) => {
49+
return {
50+
apigatewayv2: {
51+
getApis: {
52+
'us-east-1': {
53+
data: api
54+
}
55+
},
56+
getStages: {
57+
'us-east-1': 'err'
58+
}
59+
}
60+
};
61+
};
62+
63+
describe('apigatewayV2AccessLogging', function () {
64+
describe('run', function () {
65+
it('should return UNKNOWN if unable to query for API Gateway v2 APIs', function (done) {
66+
const cache = createErrorCache();
67+
apigatewayV2AccessLogging.run(cache, {} , (err, results) => {
68+
expect(results.length).to.equal(1);
69+
expect(results[0].status).to.equal(3);
70+
expect(results[0].region).to.equal('us-east-1');
71+
expect (results[0].message).to.include('Unable to query for API Gateway V2 APIs:');
72+
done();
73+
});
74+
});
75+
76+
it('should return PASS if no API Gateway Rest APIs found', function (done) {
77+
const cache = createCache([]);
78+
apigatewayV2AccessLogging.run(cache, {}, (err, results) => {
79+
expect(results.length).to.equal(1);
80+
expect(results[0].status).to.equal(0);
81+
expect(results[0].region).to.equal('us-east-1');
82+
expect (results[0].message).to.include('No API Gateway V2 APIs found');
83+
done();
84+
});
85+
});
86+
87+
it('should return PASS if no stages found', function (done) {
88+
const getApis = [
89+
{
90+
ApiId: 'api-id',
91+
name: 'TestAPI',
92+
description: 'Test API',
93+
createdDate: 1621916018,
94+
apiKeySource: 'HEADER',
95+
endpointConfiguration: {
96+
types: ['REGIONAL']
97+
}
98+
}
99+
];
100+
const getStages = [];
101+
const cache = createCache(getApis, getStages);
102+
apigatewayV2AccessLogging.run(cache,{}, (err, results) => {
103+
expect(results.length).to.equal(1);
104+
expect(results[0].status).to.equal(0);
105+
expect(results[0].region).to.equal('us-east-1');
106+
expect (results[0].message).to.include('No API Gateway V2 API Stages found');
107+
done();
108+
});
109+
});
110+
111+
it('should return PASS if Api gateway v2 stage has access logging enabled', function (done) {
112+
const getApis = [
113+
{
114+
ApiId: 'api-id',
115+
name: 'TestAPI',
116+
description: 'Test API',
117+
createdDate: 1621916018,
118+
apiKeySource: 'HEADER',
119+
endpointConfiguration: {
120+
types: ['REGIONAL']
121+
}
122+
}
123+
];
124+
const getStages = [
125+
{
126+
"AutoDeploy": true,
127+
"CreatedDate": "2023-12-11T20:07:28.000Z",
128+
"DefaultRouteSettings": {
129+
"DetailedMetricsEnabled": false
130+
},
131+
"DeploymentId": "biw5qf",
132+
"Description": "Created by AWS Lambda",
133+
"LastDeploymentStatusMessage": "Successfully deployed stage with deployment ID 'biw5qf'",
134+
"LastUpdatedDate": "2023-12-11T20:07:29.000Z",
135+
"RouteSettings": {},
136+
"StageName": "default",
137+
"StageVariables": {},
138+
"Tags": {},
139+
"AccessLogSetting": {
140+
"LogArn": "arn:aws:1234:log"
141+
}
142+
}
143+
];
144+
const cache = createCache(getApis, getStages);
145+
apigatewayV2AccessLogging.run(cache,{}, (err, results) => {
146+
expect(results.length).to.equal(1);
147+
expect(results[0].status).to.equal(0);
148+
expect(results[0].region).to.equal('us-east-1');
149+
expect(results[0].message).to.include('API Gateway V2 API stage has access logging enabled')
150+
done();
151+
});
152+
});
153+
154+
it('should return PASS if Api gateway v2 stage has access logging enabled', function (done) {
155+
const getApis = [
156+
{
157+
ApiId: 'api-id',
158+
name: 'TestAPI',
159+
description: 'Test API',
160+
createdDate: 1621916018,
161+
apiKeySource: 'HEADER',
162+
endpointConfiguration: {
163+
types: ['REGIONAL']
164+
}
165+
}
166+
];
167+
const getStages = [
168+
{
169+
"AutoDeploy": true,
170+
"CreatedDate": "2023-12-11T20:07:28.000Z",
171+
"DefaultRouteSettings": {
172+
"DetailedMetricsEnabled": false
173+
},
174+
"DeploymentId": "biw5qf",
175+
"Description": "Created by AWS Lambda",
176+
"LastDeploymentStatusMessage": "Successfully deployed stage with deployment ID 'biw5qf'",
177+
"LastUpdatedDate": "2023-12-11T20:07:29.000Z",
178+
"RouteSettings": {},
179+
"StageName": "default",
180+
"StageVariables": {},
181+
"Tags": {},
182+
}
183+
];
184+
const cache = createCache(getApis, getStages);
185+
apigatewayV2AccessLogging.run(cache,{}, (err, results) => {
186+
expect(results.length).to.equal(1);
187+
expect(results[0].status).to.equal(2);
188+
expect(results[0].region).to.equal('us-east-1');
189+
expect(results[0].message).to.include('API Gateway V2 API stage does not have access logging enabled')
190+
done();
191+
});
192+
});
193+
194+
it('should return UNKNOWN if unable to query for api stages', function (done) {
195+
const getApis = [
196+
{
197+
ApiId: 'api-id',
198+
name: 'TestAPI',
199+
description: 'Test API',
200+
createdDate: 1621916018,
201+
apiKeySource: 'HEADER',
202+
endpointConfiguration: {
203+
types: ['REGIONAL']
204+
}
205+
}
206+
];
207+
208+
const cache = createUnknownForStage(getApis);
209+
apigatewayV2AccessLogging.run(cache,{}, (err, results) => {
210+
expect(results.length).to.equal(1);
211+
expect(results[0].status).to.equal(3);
212+
expect(results[0].region).to.equal('us-east-1');
213+
expect(results[0].message).to.include('Unable to query for API Gateway V2 API Stages:')
214+
done();
215+
});
216+
});
217+
});
218+
});

0 commit comments

Comments
 (0)