Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into remove-codestar-and-fix-op…
Browse files Browse the repository at this point in the history
…ensalt-severity
  • Loading branch information
tzurielweisberg committed Feb 27, 2025
2 parents 19b4271 + e5fbb4e commit 74ca959
Show file tree
Hide file tree
Showing 34 changed files with 978 additions and 173 deletions.
49 changes: 49 additions & 0 deletions collectors/aws/accessanalyzer/listFindingsV2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
var AWS = require('aws-sdk');
var async = require('async');
var helpers = require(__dirname + '/../../../helpers/aws');

module.exports = function(AWSConfig, collection, retries, callback) {
var accessanalyzer = new AWS.AccessAnalyzer(AWSConfig);
async.eachLimit(collection.accessanalyzer.listAnalyzers[AWSConfig.region].data, 15, function(analyzer, cb) {
collection.accessanalyzer.listFindingsV2[AWSConfig.region][analyzer.arn] = {};
var params = {
analyzerArn: analyzer.arn
};

var paginating = false;
var paginateCb = function(err, data) {
if (err) collection.accessanalyzer.listFindingsV2[AWSConfig.region][analyzer.arn].err = err;

if (!data) return cb();

if (paginating && data.findings && data.findings.length &&
collection.accessanalyzer.listFindingsV2[AWSConfig.region][analyzer.arn].data.findings &&
collection.accessanalyzer.listFindingsV2[AWSConfig.region][analyzer.arn].data.findings.length) {
collection.accessanalyzer.listFindingsV2[AWSConfig.region][analyzer.arn].data.findings = collection.accessanalyzer.listFindings[AWSConfig.region][analyzer.arn].data.findings.concat(data.findings);
} else {
collection.accessanalyzer.listFindingsV2[AWSConfig.region][analyzer.arn].data = data;
}

if (data.nextToken && data.nextToken.length) {
paginating = true;
return execute(data.nextToken);
}

cb();
};

function execute(nextToken) { // eslint-disable-line no-inner-declarations
var localParams = JSON.parse(JSON.stringify(params || {}));
if (nextToken) localParams['nextToken'] = nextToken;
if (nextToken) {
helpers.makeCustomCollectorCall(accessanalyzer, 'listFindingsV2', localParams, retries, null, null, null, paginateCb);
} else {
helpers.makeCustomCollectorCall(accessanalyzer, 'listFindingsV2', params, retries, null, null, null, paginateCb);
}
}

execute();
}, function(){
callback();
});
};
4 changes: 3 additions & 1 deletion exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ module.exports = {
'lambdaDeadLetterQueue' : require(__dirname + '/plugins/aws/lambda/lambdaDeadLetterQueue.js'),
'lambdaEnhancedMonitoring' : require(__dirname + '/plugins/aws/lambda/lambdaEnhancedMonitoring.js'),
'lambdaUniqueExecutionRole' : require(__dirname + '/plugins/aws/lambda/lambdaUniqueExecutionRole.js'),
'lambdaNetworkExposure' : require(__dirname + '/plugins/aws/lambda/lambdaNetworkExposure.js'),

'webServerPublicAccess' : require(__dirname + '/plugins/aws/mwaa/webServerPublicAccess.js'),
'environmentAdminPrivileges' : require(__dirname + '/plugins/aws/mwaa/environmentAdminPrivileges.js'),
Expand Down Expand Up @@ -1000,6 +1001,7 @@ module.exports = {
'disableFTPDeployments' : require(__dirname + '/plugins/azure/appservice/disableFTPDeployments.js'),
'accessControlAllowCredential' : require(__dirname + '/plugins/azure/appservice/accessControlAllowCredential.js'),
'appServiceDiagnosticLogs' : require(__dirname + '/plugins/azure/appservice/appServiceDiagnosticLogs.js'),
'functionAppNetworkExposure' : require(__dirname + '/plugins/azure/appservice/functionAppNetworkExposure.js'),

'rbacEnabled' : require(__dirname + '/plugins/azure/kubernetesservice/rbacEnabled.js'),
'aksManagedIdentity' : require(__dirname + '/plugins/azure/kubernetesservice/aksManagedIdentity.js'),
Expand Down Expand Up @@ -1605,8 +1607,8 @@ module.exports = {
'cloudFunctionLabelsAdded' : require(__dirname + '/plugins/google/cloudfunctions/cloudFunctionLabelsAdded.js'),
'cloudFunctionOldRuntime' : require(__dirname + '/plugins/google/cloudfunctions/cloudFunctionOldRuntime.js'),
'functionAllUsersPolicy' : require(__dirname + '/plugins/google/cloudfunctions/functionAllUsersPolicy.js'),

'serverlessVPCAccess' : require(__dirname + '/plugins/google/cloudfunctions/serverlessVPCAccess.js'),
'cloudFunctionNetworkExposure' : require(__dirname + '/plugins/google/cloudfunctions/cloudFunctionNetworkExposure.js'),

'computeAllowedExternalIPs' : require(__dirname + '/plugins/google/cloudresourcemanager/computeAllowedExternalIPs.js'),
'disableAutomaticIAMGrants' : require(__dirname + '/plugins/google/cloudresourcemanager/disableAutomaticIAMGrants.js'),
Expand Down
2 changes: 1 addition & 1 deletion helpers/asl/asl-1.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var parse = function(obj, path, region, cloud, accountId, resourceId) {
return parse(obj[localPath], path);
} else return ['not set'];
} else if (!Array.isArray(obj) && path && path.length) {
if (obj[path]) return [obj[path]];
if (obj[path] || typeof obj[path] === 'boolean') return [obj[path]];
else {
if (cloud==='aws' && path.startsWith('arn:aws')) {
const template_string = path;
Expand Down
6 changes: 6 additions & 0 deletions helpers/aws/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1833,8 +1833,14 @@ var postcalls = [
reliesOnCall: 'listAnalyzers',
override: true
},
listFindingsV2: {
reliesOnService: 'accessanalyzer',
reliesOnCall: 'listAnalyzers',
override: true
},
sendIntegration: serviceMap['IAM'][0]
},

APIGateway: {
getStages: {
reliesOnService: 'apigateway',
Expand Down
73 changes: 35 additions & 38 deletions helpers/aws/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1239,7 +1239,6 @@ var getAttachedELBs = function(cache, source, region, resourceId, lbField, lbAt
return elbs;
};


var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs, region, results, resource) {
var internetExposed = '';
var isSubnetPrivate = false;
Expand All @@ -1249,41 +1248,41 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs
if (resource.functionUrlConfig && resource.functionUrlConfig.data) {
if (resource.functionUrlConfig.data.AuthType === 'NONE') {
internetExposed += 'public function URL';
} else if (resource.functionUrlConfig.data.AuthType === 'AWS_IAM' &&
resource.functionPolicy && resource.functionPolicy.data) {
} else if (resource.functionUrlConfig.data.AuthType === 'AWS_IAM' &&
resource.functionPolicy && resource.functionPolicy.data) {
let authConfig = resource.functionPolicy.data;
if (authConfig.Policy) {
let statements = normalizePolicyDocument(authConfig.Policy);

if (statements) {
let hasDenyAll = false;
let hasPublicAllow = false;
let hasRestrictiveConditions = false;

for (let statement of statements) {
// Check for explicit deny statements first
if (statement.Effect === 'Deny') {
// Check if there's a deny for all principals
if ((!statement.Condition || Object.keys(statement.Condition).length === 0) &&
if ((!statement.Condition || Object.keys(statement.Condition).length === 0) &&
globalPrincipal(statement.Principal)) {
hasDenyAll = true;
break;
}

// Check for deny with IP restrictions
if (statement.Condition &&
(statement.Condition['NotIpAddress'] ||
statement.Condition['IpAddress'])) {
if (statement.Condition &&
(statement.Condition['NotIpAddress'] ||
statement.Condition['IpAddress'])) {
hasRestrictiveConditions = true;
}
} else if (statement.Effect === 'Allow') {
// Skip if the statement doesn't include relevant Lambda actions
if (!statement.Action ||
(!Array.isArray(statement.Action) ?
if (!statement.Action ||
(!Array.isArray(statement.Action) ?
!statement.Action.includes('lambda:InvokeFunctionUrl') :
!statement.Action.some(action =>
action === '*' ||
action === 'lambda:*' ||
!statement.Action.some(action =>
action === '*' ||
action === 'lambda:*' ||
action === 'lambda:InvokeFunctionUrl'
))) {
continue;
Expand All @@ -1303,17 +1302,17 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs
'aws:PrincipalArn',
'aws:SourceAccount'
];
const hasRestriction = restrictiveConditions.some(condition =>
Object.keys(statement.Condition).some(key =>

const hasRestriction = restrictiveConditions.some(condition =>
Object.keys(statement.Condition).some(key =>
key.toLowerCase().includes(condition.toLowerCase())
)
);

if (hasRestriction) {
hasRestrictiveConditions = true;
} else if (statement.Condition['StringEquals'] &&
statement.Condition['StringEquals']['lambda:FunctionUrlAuthType'] === 'NONE') {
statement.Condition['StringEquals']['lambda:FunctionUrlAuthType'] === 'NONE') {
hasPublicAllow = true;
}
}
Expand All @@ -1323,8 +1322,8 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs

// Only mark as exposed if we have a public allow and no restrictions
if (hasPublicAllow && !hasDenyAll && !hasRestrictiveConditions) {
internetExposed += internetExposed.length ?
', function URL with global IAM access' :
internetExposed += internetExposed.length ?
', function URL with global IAM access' :
'function URL with global IAM access';
}
}
Expand Down Expand Up @@ -1352,19 +1351,19 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs
['apigateway', 'getIntegration', region, api.id]);

if (!getIntegration || getIntegration.err || !Object.keys(getIntegration).length) continue;

for (let 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 &&
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}`;
}
}
}
}
}
}
Expand All @@ -1375,7 +1374,7 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs
}

if (!resource.functionArn) {
// Scenario 1: check if resource is in a private subnet
// Scenario 1: check if resource is in a private subnet
let subnetRouteTableMap, privateSubnets;
var describeSubnets = helpers.addSource(cache, source,
['ec2', 'describeSubnets', region]);
Expand Down Expand Up @@ -1500,15 +1499,13 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs
if (elbs && elbs.length) {
if (!describeSecurityGroups || !describeSecurityGroups.data) {
describeSecurityGroups = helpers.addSource(cache, source,
['ec2', 'describeSecurityGroups', region]);
['ec2', 'describeSecurityGroups', region]);
}

elbs.forEach(lb => {
let isLBPublic = false;
if (lb.Scheme && lb.Scheme.toLowerCase() === 'internet-facing') {
if (lb.SecurityGroups && lb.SecurityGroups.length) {
var describeSecurityGroups = helpers.addSource(cache, source,
['ec2', 'describeSecurityGroups', region]);
if (describeSecurityGroups &&
!describeSecurityGroups.err && describeSecurityGroups.data && describeSecurityGroups.data.length) {
let elbSGs = describeSecurityGroups.data.filter(sg => lb.SecurityGroups.includes(sg.GroupId));
Expand All @@ -1533,7 +1530,7 @@ var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs

let getLambdaTargetELBs = function(cache, source, region) {
let lambdaELBMap = {};

var describeLoadBalancersv2 = helpers.addSource(cache, source,
['elbv2', 'describeLoadBalancers', region]);

Expand All @@ -1545,18 +1542,18 @@ let getLambdaTargetELBs = function(cache, source, region) {
var describeTargetGroups = helpers.addSource(cache, source,
['elbv2', 'describeTargetGroups', region, lb.DNSName]);

if (!describeTargetGroups || describeTargetGroups.err || !describeTargetGroups.data ||
if (!describeTargetGroups || describeTargetGroups.err || !describeTargetGroups.data ||
!describeTargetGroups.data.TargetGroups) return;

describeTargetGroups.data.TargetGroups.forEach(tg => {
var describeTargetHealth = helpers.addSource(cache, source,
['elbv2', 'describeTargetHealth', region, tg.TargetGroupArn]);

if (!describeTargetHealth || describeTargetHealth.err || !describeTargetHealth.data ||
if (!describeTargetHealth || describeTargetHealth.err || !describeTargetHealth.data ||
!describeTargetHealth.data.TargetHealthDescriptions) return;

describeTargetHealth.data.TargetHealthDescriptions.forEach(target => {
if (target.Target && target.Target.Id &&
if (target.Target && target.Target.Id &&
target.Target.Id.startsWith('arn:aws:lambda')) {
if (!lambdaELBMap[target.Target.Id]) {
lambdaELBMap[target.Target.Id] = [];
Expand All @@ -1567,21 +1564,21 @@ let getLambdaTargetELBs = function(cache, source, region) {
targetGroupArn: tg.TargetGroupArn,
targets: [target.Target]
});

// Check if there's an active listener for this target group
let hasListener = false;
var describeListeners = helpers.addSource(cache, source,
['elbv2', 'describeListeners', region, lb.DNSName]);
if (describeListeners && describeListeners.data &&

if (describeListeners && describeListeners.data &&
describeListeners.data.Listeners) {
hasListener = describeListeners.data.Listeners.some(listener =>
listener.DefaultActions.some(action =>
action.TargetGroupArn === tg.TargetGroupArn
)
);
}

if (hasListener) {
lambdaELBMap[target.Target.Id].push(lb);
}
Expand Down
Loading

0 comments on commit 74ca959

Please sign in to comment.