diff --git a/helpers/aws/functions.js b/helpers/aws/functions.js index 69209ea991..f1a08ca5ca 100644 --- a/helpers/aws/functions.js +++ b/helpers/aws/functions.js @@ -1257,27 +1257,39 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs } // Check API Gateway exposure - if (resource.functionPolicy && resource.functionPolicy.data && - resource.functionPolicy.data.Policy) { - let statements = helpers.normalizePolicyDocument(resource.functionPolicy.data.Policy); - - for (let statement of statements) { - if (statement.Principal && statement.Principal.Service === 'apigateway.amazonaws.com') { - let getRestApis = helpers.addSource(cache, source, - ['apigateway', 'getRestApis', region]); - - if (getRestApis && getRestApis.data && getRestApis.data.items) { - let apiId = getApiIdFromArn(statement.SourceArn); - let api = getRestApis.data.items.find(a => a.id === apiId); - - if (api && api.endpointConfiguration && - (api.endpointConfiguration.types.includes('EDGE') || - api.endpointConfiguration.types.includes('REGIONAL'))) { - internetExposed += internetExposed.length ? - `, API Gateway ${api.name}` : `API Gateway ${api.name}`; - } + let getRestApis = helpers.addSource(cache, source, + ['apigateway', 'getRestApis', region]); + + if (getRestApis && getRestApis.data) { + for (let api of getRestApis.data) { + + if (!api.id || !api.name) continue; + + // Get stages to check if API is deployed + let getStages = helpers.addSource(cache, source, + ['apigateway', 'getStages', region, api.id]); + + // Only include if API has at least one stage deployed + if (!getStages || getStages.err || !getStages.data || !getStages.data.item || !getStages.data.item.length) continue; + + // Get integrations for this API + let getIntegration = helpers.addSource(cache, source, + ['apigateway', 'getIntegration', region, api.id]); + + if (!getIntegration || getIntegration.err || !Object.keys(getIntegration).length) continue; + + for (apiResource of Object.values(getIntegration)) { + // Check if any integration points to this Lambda function + let lambdaIntegrations = Object.values(apiResource).filter(integration => { + return integration && integration.data && (integration.data.type === 'AWS' || integration.data.type === 'AWS_PROXY') && + integration.data.uri && + integration.data.uri.includes(resource.functionArn); + }); + + if (lambdaIntegrations.length) { + internetExposed += internetExposed.length ? `, API Gateway ${api.name}` : `API Gateway ${api.name}`; } - } + } } } diff --git a/plugins/aws/lambda/lambdaNetworkExposure.js b/plugins/aws/lambda/lambdaNetworkExposure.js index 850330bffd..d1aad36cde 100644 --- a/plugins/aws/lambda/lambdaNetworkExposure.js +++ b/plugins/aws/lambda/lambdaNetworkExposure.js @@ -10,7 +10,8 @@ module.exports = { more_info: 'Lambda functions can be exposed to the internet through Function URLs with public access policies or through API Gateway integrations. It\'s important to ensure these endpoints are properly secured.', link: 'https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html', recommended_action: 'Ensure Lambda Function URLs have proper authorization configured and API Gateway integrations use appropriate security measures.', - apis: ['Lambda:listFunctions', 'Lambda:getFunctionUrlConfig', 'Lambda:getPolicy', 'APIGateway:getRestApis'], + apis: ['Lambda:listFunctions', 'Lambda:getFunctionUrlConfig', 'Lambda:getPolicy', + 'APIGateway:getRestApis','APIGateway:getResources', 'APIGateway:getStages', 'APIGateway:getIntegration'], realtime_triggers: ['lambda:CreateFunctionUrlConfig', 'lambda:UpdateFunctionUrlConfig', 'lambda:DeleteFunctionUrlConfig', 'lambda:AddPermission', 'lambda:RemovePermission', 'apigateway:CreateRestApi', 'apigateway:DeleteRestApi', 'apigateway:UpdateRestApi', @@ -51,7 +52,8 @@ module.exports = { let lambdaResource = { functionUrlConfig: getFunctionUrlConfig, - functionPolicy: getPolicy + functionPolicy: getPolicy, + functionArn: lambda.FunctionArn }; let internetExposed = helpers.checkNetworkExposure(cache, source, [], [], [], region, results, lambdaResource);