Skip to content

Commit 172e3fc

Browse files
authored
Merge pull request aquasecurity#2142 from AkhtarAmir/Azure/Public-access-plugins
azure/Public-access-plugins
2 parents 94ab539 + e114025 commit 172e3fc

10 files changed

+1288
-92
lines changed

exports.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,8 @@ module.exports = {
894894
'postgresqlPrivateEndpoints' : require(__dirname + '/plugins/azure/postgresqlserver/postgresqlPrivateEndpoints.js'),
895895
'azureServicesAccessDisabled' : require(__dirname + '/plugins/azure/postgresqlserver/azureServicesAccessDisabled.js'),
896896
'postgresqlTlsVersion' : require(__dirname + '/plugins/azure/postgresqlserver/postgresqlTlsVersion.js'),
897+
'postgresqlServerPublicAccess' : require(__dirname + '/plugins/azure/postgresqlserver/postgresqlServerPublicAccess.js'),
898+
897899
'flexibleServerPrivateAccess' : require(__dirname + '/plugins/azure/postgresqlserver/flexibleServerPrivateAccess'),
898900
'diagnosticLoggingEnabled' : require(__dirname + '/plugins/azure/postgresqlserver/diagnosticLoggingEnabled.js'),
899901
'flexibleServerLogDisconnections': require(__dirname + '/plugins/azure/postgresqlserver/flexibleServerLogDisconnections.js'),
@@ -905,6 +907,7 @@ module.exports = {
905907
'flexibleServerLogDuration' : require(__dirname + '/plugins/azure/postgresqlserver/flexibleServerLogDuration.js'),
906908
'flexibleServerConnectionThrottle': require(__dirname + '/plugins/azure/postgresqlserver/flexibleServerConnectionThrottle.js'),
907909
'flexibleServerATP' : require(__dirname + '/plugins/azure/postgresqlserver/flexibleServerATP.js'),
910+
'postgresqlFlexibleServerPublicAccess': require(__dirname + '/plugins/azure/postgresqlserver/postgresqlFlexibleServerPublicAccess.js'),
908911

909912
'openOracleAutoDataWarehouse' : require(__dirname + '/plugins/azure/networksecuritygroups/openOracleAutoDataWarehouse.js'),
910913
'nsgFlowLogsEnabled' : require(__dirname + '/plugins/azure/networksecuritygroups/nsgFlowLogsEnabled.js'),
@@ -1083,7 +1086,8 @@ module.exports = {
10831086
'keyVaultHasTags' : require(__dirname + '/plugins/azure/keyvaults/keyVaultHasTags.js'),
10841087
'keyVaultsPrivateEndpoint' : require(__dirname + '/plugins/azure/keyvaults/keyVaultsPrivateEndpoint.js'),
10851088
'kvLogAnalyticsEnabled' : require(__dirname + '/plugins/azure/keyvaults/kvLogAnalyticsEnabled.js'),
1086-
1089+
'keyVaultPublicAccess' : require(__dirname + '/plugins/azure/keyvaults/keyVaultPublicAccess.js'),
1090+
10871091
'advancedThreatProtection' : require(__dirname + '/plugins/azure/cosmosdb/advancedThreatProtection.js'),
10881092
'cosmosdbDiagnosticLogs' : require(__dirname + '/plugins/azure/cosmosdb/cosmosdbDiagnosticLogs.js'),
10891093
'cosmosPublicAccessDisabled' : require(__dirname + '/plugins/azure/cosmosdb/cosmosPublicAccessDisabled.js'),
@@ -1746,5 +1750,5 @@ module.exports = {
17461750
'securityAgentInstalled' : require(__dirname + '/plugins/alibaba/securitycenter/securityAgentInstalled.js'),
17471751
'securityNotificationsEnabled' : require(__dirname + '/plugins/alibaba/securitycenter/securityNotificationsEnabled.js'),
17481752
'vulnerabilityScanEnabled' : require(__dirname + '/plugins/alibaba/securitycenter/vulnerabilityScanEnabled.js')
1749-
}
1753+
}
17501754
};

helpers/azure/api.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ var calls = {
375375
},
376376
vaults: {
377377
list: {
378-
url: 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.KeyVault/vaults?api-version=2019-09-01'
378+
url: 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.KeyVault/vaults?api-version=2023-07-01'
379379
},
380380
sendIntegration: serviceMap['Key Vaults'],
381381
},
@@ -1099,6 +1099,11 @@ var postcalls = {
10991099
reliesOnPath: 'servers.listPostgresFlexibleServer',
11001100
properties: ['id'],
11011101
url: 'https://management.azure.com/{id}/firewallRules?api-version=2022-12-01'
1102+
},
1103+
listByFlexibleServerMysql: {
1104+
reliesOnPath: 'servers.listMysqlFlexibleServer',
1105+
properties: ['id'],
1106+
url: 'https://management.azure.com/{id}/firewallRules?api-version=2021-05-01'
11021107
}
11031108
},
11041109
outboundFirewallRules: {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
var async = require('async');
2+
const helpers = require('../../../helpers/azure');
3+
4+
module.exports = {
5+
title: 'Key Vault Public Access',
6+
category: 'Key Vault',
7+
domain: 'Security',
8+
severity: 'High',
9+
description: 'Ensures that Azure Key Vaults do not allow unrestricted public access',
10+
more_info: 'Azure Key Vaults should be configured to restrict public access to protect sensitive data. This can be achieved by either disabling public network access or implementing strict network rules.',
11+
recommended_action: 'Modify Key Vault network settings to disable public access or appropriate configure network rules.',
12+
link: 'https://learn.microsoft.com/en-us/azure/key-vault/general/network-security',
13+
apis: ['vaults:list'],
14+
settings: {
15+
keyvault_allowed_ips: {
16+
name: 'Key Vault Allowed IPs',
17+
description: 'Comma-separated list of IP addresses that are explicitly allowed to access Key Vaults',
18+
regex: '^(?:\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(?:/\\d{1,2})?(?:,\\s*)?)+$',
19+
default: ''
20+
}
21+
},
22+
realtime_triggers: ['microsoft.keyvault:vaults:write', 'microsoft.keyvault:vaults:delete'],
23+
24+
run: function(cache, settings, callback) {
25+
var results = [];
26+
var source = {};
27+
var locations = helpers.locations(settings.govcloud);
28+
29+
var config = {
30+
keyvault_allowed_ips: settings.keyvault_allowed_ips || this.settings.keyvault_allowed_ips.default
31+
};
32+
33+
var allowedIps = [];
34+
if (config.keyvault_allowed_ips && config.keyvault_allowed_ips.length) {
35+
allowedIps = config.keyvault_allowed_ips.split(',').map(ip => ip.trim());
36+
}
37+
var checkAllowedIps = allowedIps.length > 0;
38+
39+
async.each(locations.vaults, function(location, rcb) {
40+
var vaults = helpers.addSource(cache, source,
41+
['vaults', 'list', location]);
42+
43+
if (!vaults) return rcb();
44+
45+
if (vaults.err || !vaults.data) {
46+
helpers.addResult(results, 3,
47+
'Unable to query for Key Vaults: ' + helpers.addError(vaults), location);
48+
return rcb();
49+
}
50+
51+
if (!vaults.data.length) {
52+
helpers.addResult(results, 0, 'No Key Vaults found', location);
53+
return rcb();
54+
}
55+
56+
vaults.data.forEach(function(vault) {
57+
if (!vault.id) return;
58+
59+
if (vault &&
60+
vault.publicNetworkAccess &&
61+
vault.publicNetworkAccess.toLowerCase() === 'disabled') {
62+
helpers.addResult(results, 0,
63+
'Key Vault is protected from outside traffic',
64+
location, vault.id);
65+
return;
66+
}
67+
68+
69+
if (vault && vault.networkAcls) {
70+
var networkAcls = vault.networkAcls;
71+
var defaultAction = networkAcls.defaultAction ? networkAcls.defaultAction.toLowerCase() : null;
72+
73+
if (!defaultAction || defaultAction === 'allow') {
74+
helpers.addResult(results, 2,
75+
'Key Vault is open to outside traffic',
76+
location, vault.id);
77+
return;
78+
}
79+
80+
if (defaultAction === 'deny') {
81+
var ipRules = networkAcls.ipRules || [];
82+
var hasPublicAccess = false;
83+
var publicAccessFound = [];
84+
85+
for (var rule of ipRules) {
86+
if (checkAllowedIps) {
87+
if ((rule.value === '0.0.0.0/0' || rule.value === '0.0.0.0') &&
88+
!allowedIps.includes(rule.value)) {
89+
hasPublicAccess = true;
90+
publicAccessFound.push(rule.value);
91+
}
92+
} else if (rule.value === '0.0.0.0/0' || rule.value === '0.0.0.0') {
93+
hasPublicAccess = true;
94+
publicAccessFound.push(rule.value);
95+
}
96+
}
97+
98+
if (hasPublicAccess) {
99+
helpers.addResult(results, 2,
100+
`Key Vault is open to outside traffic through IP rules: ${publicAccessFound.join(', ')}`,
101+
location, vault.id);
102+
} else {
103+
var message = 'Key Vault is protected from outside traffic';
104+
helpers.addResult(results, 0, message, location, vault.id);
105+
}
106+
}
107+
} else {
108+
helpers.addResult(results, 2,
109+
'Key Vault is open to outside traffic',
110+
location, vault.id);
111+
}
112+
});
113+
114+
rcb();
115+
}, function() {
116+
callback(null, results, source);
117+
});
118+
}
119+
};
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
var expect = require('chai').expect;
2+
var keyVaultPublicAccess = require('./keyVaultPublicAccess');
3+
4+
const vaults = [
5+
{
6+
"id": "/subscriptions/123/resourceGroups/test/providers/Microsoft.KeyVault/vaults/test1",
7+
"name": "test1",
8+
"type": "Microsoft.KeyVault/vaults",
9+
"publicNetworkAccess": "Disabled"
10+
11+
},
12+
{
13+
"id": "/subscriptions/123/resourceGroups/test/providers/Microsoft.KeyVault/vaults/test2",
14+
"name": "test2",
15+
"type": "Microsoft.KeyVault/vaults",
16+
"publicNetworkAccess": "Enabled",
17+
"networkAcls": {
18+
"defaultAction": "Deny",
19+
"ipRules": [
20+
{
21+
"value": "10.0.0.0/16"
22+
}
23+
]
24+
}
25+
26+
},
27+
{
28+
"id": "/subscriptions/123/resourceGroups/test/providers/Microsoft.KeyVault/vaults/test3",
29+
"name": "test3",
30+
"type": "Microsoft.KeyVault/vaults",
31+
"publicNetworkAccess": "Enabled",
32+
"networkAcls": {
33+
"defaultAction": "Allow",
34+
"ipRules": []
35+
}
36+
37+
},
38+
{
39+
"id": "/subscriptions/123/resourceGroups/test/providers/Microsoft.KeyVault/vaults/test4",
40+
"name": "test4",
41+
"type": "Microsoft.KeyVault/vaults",
42+
"publicNetworkAccess": "Enabled",
43+
"networkAcls": {
44+
"defaultAction": "Deny",
45+
"ipRules": [
46+
{
47+
"value": "0.0.0.0/0"
48+
}
49+
]
50+
}
51+
},
52+
{
53+
"id": "/subscriptions/123/resourceGroups/test/providers/Microsoft.KeyVault/vaults/test6",
54+
"name": "test6",
55+
"type": "Microsoft.KeyVault/vaults",
56+
"publicNetworkAccess": "Enabled"
57+
58+
},
59+
{
60+
"id": "/subscriptions/123/resourceGroups/test/providers/Microsoft.KeyVault/vaults/test7",
61+
"name": "test7",
62+
"type": "Microsoft.KeyVault/vaults",
63+
"publicNetworkAccess": "Enabled",
64+
"networkAcls": {
65+
"defaultAction": "Deny",
66+
"ipRules": [
67+
{
68+
"value": "192.168.1.1"
69+
}
70+
]
71+
72+
}
73+
}
74+
];
75+
76+
const createCache = (vaults) => {
77+
return {
78+
vaults: {
79+
list: {
80+
'eastus': {
81+
data: vaults
82+
}
83+
}
84+
}
85+
};
86+
};
87+
88+
const createErrorCache = () => {
89+
return {
90+
vaults: {
91+
list: {
92+
'eastus': {
93+
err: {
94+
message: 'error loading vaults'
95+
}
96+
}
97+
}
98+
}
99+
};
100+
};
101+
102+
describe('keyVaultPublicAccess', function () {
103+
describe('run', function () {
104+
it('should give passing result if no key vaults found', function (done) {
105+
const cache = createCache([]);
106+
keyVaultPublicAccess.run(cache, {}, (err, results) => {
107+
expect(results.length).to.equal(1);
108+
expect(results[0].status).to.equal(0);
109+
expect(results[0].message).to.include('No Key Vaults found');
110+
expect(results[0].region).to.equal('eastus');
111+
done();
112+
});
113+
});
114+
115+
it('should give unknown result if unable to query for key vaults', function (done) {
116+
const cache = createErrorCache();
117+
keyVaultPublicAccess.run(cache, {}, (err, results) => {
118+
expect(results.length).to.equal(1);
119+
expect(results[0].status).to.equal(3);
120+
expect(results[0].message).to.include('Unable to query for Key Vaults');
121+
expect(results[0].region).to.equal('eastus');
122+
done();
123+
});
124+
});
125+
126+
it('should give passing result if public network access is disabled', function (done) {
127+
const cache = createCache([vaults[0]]);
128+
keyVaultPublicAccess.run(cache, {}, (err, results) => {
129+
expect(results.length).to.equal(1);
130+
expect(results[0].status).to.equal(0);
131+
expect(results[0].message).to.include('Key Vault is protected from outside traffic');
132+
expect(results[0].region).to.equal('eastus');
133+
done();
134+
});
135+
});
136+
137+
it('should give passing result if default action is deny and no public IPs allowed', function (done) {
138+
const cache = createCache([vaults[1]]);
139+
keyVaultPublicAccess.run(cache, {}, (err, results) => {
140+
expect(results.length).to.equal(1);
141+
expect(results[0].status).to.equal(0);
142+
expect(results[0].message).to.include('Key Vault is protected from outside traffic');
143+
expect(results[0].region).to.equal('eastus');
144+
done();
145+
});
146+
});
147+
148+
it('should give failing result if default action is allow', function (done) {
149+
const cache = createCache([vaults[2]]);
150+
keyVaultPublicAccess.run(cache, {}, (err, results) => {
151+
expect(results.length).to.equal(1);
152+
expect(results[0].status).to.equal(2);
153+
expect(results[0].message).to.include('Key Vault is open to outside traffic');
154+
expect(results[0].region).to.equal('eastus');
155+
done();
156+
});
157+
});
158+
159+
it('should give failing result if IPv4 public access is allowed', function (done) {
160+
const cache = createCache([vaults[3]]);
161+
keyVaultPublicAccess.run(cache, {}, (err, results) => {
162+
expect(results.length).to.equal(1);
163+
expect(results[0].status).to.equal(2);
164+
expect(results[0].message).to.include('Key Vault is open to outside traffic');
165+
expect(results[0].region).to.equal('eastus');
166+
done();
167+
});
168+
});
169+
170+
it('should give failing result if no network ACLs configured', function (done) {
171+
const cache = createCache([vaults[4]]);
172+
keyVaultPublicAccess.run(cache, {}, (err, results) => {
173+
expect(results.length).to.equal(1);
174+
expect(results[0].status).to.equal(2);
175+
expect(results[0].message).to.include('Key Vault is open to outside traffic');
176+
expect(results[0].region).to.equal('eastus');
177+
done();
178+
});
179+
});
180+
181+
it('should give passing result if IP is in allowed list', function (done) {
182+
const cache = createCache([vaults[5]]);
183+
keyVaultPublicAccess.run(cache, { keyvault_allowed_ips: '192.168.1.1' }, (err, results) => {
184+
expect(results.length).to.equal(1);
185+
expect(results[0].status).to.equal(0);
186+
expect(results[0].message).to.include('Key Vault is protected from outside traffic');
187+
expect(results[0].region).to.equal('eastus');
188+
done();
189+
});
190+
});
191+
});
192+
});

0 commit comments

Comments
 (0)