diff --git a/collectors/alibaba/collector.js b/collectors/alibaba/collector.js index 178d0ea0ff..10438483c4 100644 --- a/collectors/alibaba/collector.js +++ b/collectors/alibaba/collector.js @@ -183,6 +183,13 @@ var postcalls = [ filterValue: ['UserName'], apiVersion: '2015-05-01' }, + ListAccessKeys: { + reliesOnService: 'ram', + reliesOnCall: 'ListUsers', + filterKey: ['UserName'], + filterValue: ['UserName'], + apiVersion: '2015-05-01' + }, ListPoliciesForUser: { reliesOnService: 'ram', reliesOnCall: 'ListUsers', diff --git a/exports.js b/exports.js index 4fd9294650..ea3787d9f4 100644 --- a/exports.js +++ b/exports.js @@ -494,7 +494,7 @@ module.exports = { 'http20Enabled' : require(__dirname + '/plugins/azure/appservice/http20Enabled.js'), 'httpsOnlyEnabled' : require(__dirname + '/plugins/azure/appservice/httpsOnlyEnabled.js'), 'tlsVersionCheck' : require(__dirname + '/plugins/azure/appservice/tlsVersionCheck.js'), - 'automatedBackupsConfigured' : require(__dirname + '/plugins/azure/appservice/automatedBackupsConfigured.js'), + 'appserviceAutomatedBackups' : require(__dirname + '/plugins/azure/appservice/appserviceAutomatedBackups.js'), 'rbacEnabled' : require(__dirname + '/plugins/azure/kubernetesservice/rbacEnabled.js'), 'aksLatestVersion' : require(__dirname + '/plugins/azure/kubernetesservice/aksLatestVersion.js'), @@ -561,6 +561,7 @@ module.exports = { 'excessivePolicies' : require(__dirname + '/plugins/oracle/identity/excessivePolicies.js'), 'excessivePolicyStatements' : require(__dirname + '/plugins/oracle/identity/excessivePolicyStatements.js'), 'policyLeastPrivilege' : require(__dirname + '/plugins/oracle/identity/policyLeastPrivilege.js'), + 'usersEmailVerified' : require(__dirname + '/plugins/oracle/identity/usersEmailVerified.js'), 'openSSH' : require(__dirname + '/plugins/oracle/networking/openSSH.js'), 'openOracleAutoDataWarehouse' : require(__dirname + '/plugins/oracle/networking/openOracleAutoDataWarehouse.js'), @@ -669,6 +670,9 @@ module.exports = { 'dbPubliclyAccessible' : require(__dirname + '/plugins/google/sql/dbPubliclyAccessible.js'), 'dbSSLEnabled' : require(__dirname + '/plugins/google/sql/dbSSLEnabled.js'), 'anyHostRootAccess' : require(__dirname + '/plugins/google/sql/anyHostRootAccess.js'), + 'postgresqlLogMinError' : require(__dirname + '/plugins/google/sql/postgresqlLogMinError.js'), + 'postgresqlLogTempFiles' : require(__dirname + '/plugins/google/sql/postgresqlLogTempFiles.js'), + 'postgresqlLogMinDuration' : require(__dirname + '/plugins/google/sql/postgresqlLogMinDuration.js'), 'postgresqlLogLockWaits' : require(__dirname + '/plugins/google/sql/postgresqlLogLockWaits.js'), 'mysqlLocalInfile' : require(__dirname + '/plugins/google/sql/mysqlLocalInfile.js'), 'postgresqlLogConnections' : require(__dirname + '/plugins/google/sql/postgresqlLogConnections.js'), @@ -735,6 +739,7 @@ module.exports = { 'inactiveUserDisabled' : require(__dirname + '/plugins/alibaba/ram/inactiveUserDisabled.js'), 'passwordRequiresUppercase' : require(__dirname + '/plugins/alibaba/ram/passwordRequiresUppercase.js'), 'usersMfaEnabled' : require(__dirname + '/plugins/alibaba/ram/usersMfaEnabled.js'), + 'accessKeysRotation' : require(__dirname + '/plugins/alibaba/ram/accessKeysRotation.js'), 'passwordNoReuse' : require(__dirname + '/plugins/alibaba/ram/passwordNoReuse.js'), 'passwordExpiry' : require(__dirname + '/plugins/alibaba/ram/passwordExpiry.js'), 'passwordBlockLogon' : require(__dirname + '/plugins/alibaba/ram/passwordBlockLogon.js'), @@ -755,6 +760,15 @@ module.exports = { 'openNetBIOS' : require(__dirname + '/plugins/alibaba/ecs/openNetBIOS.js'), 'openOracle' : require(__dirname + '/plugins/alibaba/ecs/openOracle.js'), 'dataDisksEncrypted' : require(__dirname + '/plugins/alibaba/ecs/dataDisksEncrypted.js'), + 'openCustomPorts' : require(__dirname + '/plugins/alibaba/ecs/openCustomPorts.js'), + 'openOracleAutoDataWarehouse' : require(__dirname + '/plugins/alibaba/ecs/openOracleAutoDataWarehouse.js'), + 'openSalt' : require(__dirname + '/plugins/alibaba/ecs/openSalt.js'), + 'openSMTP' : require(__dirname + '/plugins/alibaba/ecs/openSMTP.js'), + 'openSMBoTCP' : require(__dirname + '/plugins/alibaba/ecs/openSMBoTCP.js'), + 'openSQLServer' : require(__dirname + '/plugins/alibaba/ecs/openSQLServer.js'), + 'openTelnet' : require(__dirname + '/plugins/alibaba/ecs/openTelnet.js'), + 'openVNCClient' : require(__dirname + '/plugins/alibaba/ecs/openVNCClient.js'), + 'openVNCServer' : require(__dirname + '/plugins/alibaba/ecs/openVNCServer.js'), 'bucketLoggingEnabled' : require(__dirname + '/plugins/alibaba/oss/bucketLoggingEnabled.js'), 'ossBucketPrivate' : require(__dirname + '/plugins/alibaba/oss/ossBucketPrivate.js'), diff --git a/helpers/alibaba/functions.js b/helpers/alibaba/functions.js index 494218f54b..d0193f2fd3 100644 --- a/helpers/alibaba/functions.js +++ b/helpers/alibaba/functions.js @@ -11,7 +11,6 @@ function createArn(service, account, resourceType, resourceId, region) { } function findOpenPorts(cache, groups, ports, service, region, results) { - // console.log(JSON.stringify(cache, null, 2)); var found = false; for (var group of groups) { @@ -51,7 +50,7 @@ function findOpenPorts(cache, groups, ports, service, region, results) { for (let i = rangeFrom; i <= rangeTo; i++) { if (fromPort<= i && toPort >= i) { string = `some of ${permission.IpProtocol}:${port}`; - openV4Ports.push(string); + if (openV4Ports.indexOf(string) === -1) openV4Ports.push(string); found = true; break; } diff --git a/plugins/alibaba/ecs/openCustomPorts.js b/plugins/alibaba/ecs/openCustomPorts.js new file mode 100644 index 0000000000..05295f2bce --- /dev/null +++ b/plugins/alibaba/ecs/openCustomPorts.js @@ -0,0 +1,66 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open Custom Ports', + category: 'ECS', + description: 'Ensure that defined custom ports are not open to public.', + more_info: 'Security groups should restrict access to ports from known networks.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Modify the security group to ensure the defined custom ports are not exposed publicly', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + settings: { + restricted_open_ports: { + name: 'Restricted Open Ports', + description: 'Comma separated list of ports/port-ranges that should be restricted and not publicly open. Example: tcp:80,tcp:443,tcp:80-443', + regex: '[a-zA-Z0-9,:]', + default: '' + }, + }, + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var restricted_open_ports = settings.restricted_open_ports || this.settings.restricted_open_ports.default; + + if (!restricted_open_ports.length) return callback(); + + restricted_open_ports = restricted_open_ports.split(','); + + var ports = {}; + restricted_open_ports.forEach(port => { + var [protocol, portNo] = port.split(':'); + if (ports[protocol]) { + ports[protocol].push(portNo); + } else { + ports[protocol] = [portNo]; + } + }); + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, 'custom', region, results); + + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openCustomPorts.spec.js b/plugins/alibaba/ecs/openCustomPorts.spec.js new file mode 100644 index 0000000000..5f8da490e5 --- /dev/null +++ b/plugins/alibaba/ecs/openCustomPorts.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openCustomPorts = require('./openCustomPorts'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "53/80", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openCustomPorts', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openCustomPorts.run(cache, { restricted_open_ports: 'tcp:22' }, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has custom ports open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openCustomPorts.run(cache, { restricted_open_ports: 'tcp:60,tcp:65-70' }, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has custom:TCP:60, some of TCP:65-70 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openCustomPorts.run(cache, { restricted_open_ports: 'tcp:22' }, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openCustomPorts.run(cache, { restricted_open_ports: 'tcp:22' }, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openOracleAutoDataWarehouse.js b/plugins/alibaba/ecs/openOracleAutoDataWarehouse.js new file mode 100644 index 0000000000..8735f72380 --- /dev/null +++ b/plugins/alibaba/ecs/openOracleAutoDataWarehouse.js @@ -0,0 +1,47 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open Oracle Auto Data Warehouse', + category: 'ECS', + description: 'Ensure that security groups does not have TCP port 1522 for Oracle Auto Data Warehouse open to the public.', + more_info: 'While some ports such as HTTP and HTTPS are required to be open to the public to function properly, more sensitive services such as Oracle Auto Data Warehouse should be restricted to known IP addresses.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP port 1522 for Oracle Auto Data Warehouse to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [1522] + }; + + var service = 'Oracle Auto Data Warehouse'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openOracleAutoDataWarehouse.spec.js b/plugins/alibaba/ecs/openOracleAutoDataWarehouse.spec.js new file mode 100644 index 0000000000..2e0c3300b2 --- /dev/null +++ b/plugins/alibaba/ecs/openOracleAutoDataWarehouse.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openOracleAutoDataWarehouse = require('./openOracleAutoDataWarehouse'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "1522/1522", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openOracleAutoDataWarehouse', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openOracleAutoDataWarehouse.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has Oracle Auto Data Warehouse TCP 1522 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openOracleAutoDataWarehouse.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has Oracle Auto Data Warehouse:TCP:1522 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openOracleAutoDataWarehouse.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openOracleAutoDataWarehouse.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openSMBoTCP.js b/plugins/alibaba/ecs/openSMBoTCP.js new file mode 100644 index 0000000000..43227b46b4 --- /dev/null +++ b/plugins/alibaba/ecs/openSMBoTCP.js @@ -0,0 +1,47 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open SMBoTCP', + category: 'ECS', + description: 'Ensure that security groups does not have TCP port 445 for Windows SMB over TCP open to the public.', + more_info: 'While some ports such as HTTP and HTTPS are required to be open to the public to function properly, more sensitive services such as SMBoTCP should be restricted to known IP addresses.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP port 445 to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [445] + }; + + var service = 'SMBoTCP'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openSMBoTCP.spec.js b/plugins/alibaba/ecs/openSMBoTCP.spec.js new file mode 100644 index 0000000000..77e833f461 --- /dev/null +++ b/plugins/alibaba/ecs/openSMBoTCP.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openSMBoTCP = require('./openSMBoTCP'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "445/445", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openSMBoTCP', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openSMBoTCP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has SMBoTCP TCP 445 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openSMBoTCP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has SMBoTCP:TCP:445 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openSMBoTCP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openSMBoTCP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openSMTP.js b/plugins/alibaba/ecs/openSMTP.js new file mode 100644 index 0000000000..fb3a4d0c78 --- /dev/null +++ b/plugins/alibaba/ecs/openSMTP.js @@ -0,0 +1,47 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open SMTP', + category: 'ECS', + description: 'Ensure that security groups does not have TCP port 25 for SMTP open to the public.', + more_info: 'While some ports such as HTTP and HTTPS are required to be open to the public to function properly, more sensitive services such as SMTP should be restricted to known IP addresses.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP port 25 for SMTP to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [25] + }; + + var service = 'SMTP'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openSMTP.spec.js b/plugins/alibaba/ecs/openSMTP.spec.js new file mode 100644 index 0000000000..4a34508948 --- /dev/null +++ b/plugins/alibaba/ecs/openSMTP.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openSMTP = require('./openSMTP'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "25/25", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openSMTP', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openSMTP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has SMTP TCP 25 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openSMTP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has SMTP:TCP:25 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openSMTP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openSMTP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openSQLServer.js b/plugins/alibaba/ecs/openSQLServer.js new file mode 100644 index 0000000000..b5cdf643b9 --- /dev/null +++ b/plugins/alibaba/ecs/openSQLServer.js @@ -0,0 +1,48 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open SQL Server', + category: 'ECS', + description: 'Ensure that security groups does not have TCP port 1433 or UDP port 1434 for SQL Server open to the public.', + more_info: 'While some ports such as HTTP and HTTPS are required to be open to the public to function properly, more sensitive services such as SQL Server should be restricted to known IP addresses.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP port 1433 or UDP port 1434 for SQL Server to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [1433], + 'udp': [1434] + }; + + var service = 'SQL Server'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openSQLServer.spec.js b/plugins/alibaba/ecs/openSQLServer.spec.js new file mode 100644 index 0000000000..7a81475201 --- /dev/null +++ b/plugins/alibaba/ecs/openSQLServer.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openSQLServer = require('./openSQLServer'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "1433/1433", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openSQLServer', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openSQLServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has SQL Server TCP 1433 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openSQLServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has SQL Server:TCP:1433 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openSQLServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openSQLServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openSSH.js b/plugins/alibaba/ecs/openSSH.js index 6c8a025558..88924d79df 100644 --- a/plugins/alibaba/ecs/openSSH.js +++ b/plugins/alibaba/ecs/openSSH.js @@ -11,7 +11,6 @@ module.exports = { apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], run: function(cache, settings, callback) { - // console.log(JSON.stringify(cache, null, 2)); var results = []; var source = {}; var regions = helpers.regions(settings); diff --git a/plugins/alibaba/ecs/openSalt.js b/plugins/alibaba/ecs/openSalt.js new file mode 100644 index 0000000000..d7b86a2832 --- /dev/null +++ b/plugins/alibaba/ecs/openSalt.js @@ -0,0 +1,47 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open Salt', + category: 'ECS', + description: 'Ensure that security groups does not have TCP ports 4505 or 4506 for the Salt master open to the public.', + more_info: 'Active Salt vulnerabilities, CVE-2020-11651 and CVE-2020-11652 are exploiting Salt instances exposed to the internet. These ports should be closed immediately.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP ports 4505 and 4506 to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [4505, 4506] + }; + + var service = 'Salt'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openSalt.spec.js b/plugins/alibaba/ecs/openSalt.spec.js new file mode 100644 index 0000000000..b12df5bf0f --- /dev/null +++ b/plugins/alibaba/ecs/openSalt.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openSalt = require('./openSalt'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "4505/4505", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openSalt', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openSalt.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has Salt TCP 4505 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openSalt.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has Salt:TCP:4505 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openSalt.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openSalt.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openTelnet.js b/plugins/alibaba/ecs/openTelnet.js new file mode 100644 index 0000000000..0a0bd50ff8 --- /dev/null +++ b/plugins/alibaba/ecs/openTelnet.js @@ -0,0 +1,47 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open Telnet', + category: 'ECS', + description: 'Ensure that security groups does not have TCP port 23 for Telnet open to the public.', + more_info: 'While some ports such as HTTP and HTTPS are required to be open to the public to function properly, more sensitive services such as Telnet should be restricted to known IP addresses.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP port 23 for Telnet to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [23] + }; + + var service = 'Telnet'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openTelnet.spec.js b/plugins/alibaba/ecs/openTelnet.spec.js new file mode 100644 index 0000000000..dfff3a1b69 --- /dev/null +++ b/plugins/alibaba/ecs/openTelnet.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openTelnet = require('./openTelnet'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "23/23", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openTelnet', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openTelnet.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has Telnet TCP 23 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openTelnet.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has Telnet:TCP:23 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openTelnet.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openTelnet.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openVNCClient.js b/plugins/alibaba/ecs/openVNCClient.js new file mode 100644 index 0000000000..e8aa10b464 --- /dev/null +++ b/plugins/alibaba/ecs/openVNCClient.js @@ -0,0 +1,47 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open VNC Client', + category: 'ECS', + description: 'Ensure that security groups does not have TCP port 5500 for VNC Client open to the public.', + more_info: 'While some ports such as HTTP and HTTPS are required to be open to the public to function properly, more sensitive services such as VNC Client should be restricted to known IP addresses.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP port 5500 to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [5500] + }; + + var service = 'VNC Client'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openVNCClient.spec.js b/plugins/alibaba/ecs/openVNCClient.spec.js new file mode 100644 index 0000000000..d268406d38 --- /dev/null +++ b/plugins/alibaba/ecs/openVNCClient.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openVNCClient = require('./openVNCClient'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "5500/5500", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openVNCClient', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openVNCClient.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has VNC Client TCP 5500 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openVNCClient.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has VNC Client:TCP:5500 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openVNCClient.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openVNCClient.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ecs/openVNCServer.js b/plugins/alibaba/ecs/openVNCServer.js new file mode 100644 index 0000000000..2d4ac5afe3 --- /dev/null +++ b/plugins/alibaba/ecs/openVNCServer.js @@ -0,0 +1,47 @@ +var async = require('async'); +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Open VNC Server', + category: 'ECS', + description: 'Ensure that security groups does not have TCP port 5900 for VNC Server open to the public.', + more_info: 'While some ports such as HTTP and HTTPS are required to be open to the public to function properly, more sensitive services such as VNC Server should be restricted to known IP addresses.', + link: 'https://www.alibabacloud.com/help/doc-detail/25471.htm', + recommended_action: 'Restrict TCP port 5900 to known IP addresses', + apis: ['ECS:DescribeSecurityGroups', 'ECS:DescribeSecurityGroupAttribute', 'STS:GetCallerIdentity'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var ports = { + 'tcp': [5900] + }; + + var service = 'VNC Server'; + + async.each(regions.ecs, function(region, rcb){ + var describeSecurityGroups = helpers.addSource(cache, source, + ['ecs', 'DescribeSecurityGroups', region]); + + if (!describeSecurityGroups) return rcb(); + + if (describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + `Unable to describe security groups: ${helpers.addError(describeSecurityGroups)}`, region); + return rcb(); + } + + if (!describeSecurityGroups.data.length) { + helpers.addResult(results, 0, 'No security groups found', region); + return rcb(); + } + + helpers.findOpenPorts(cache, describeSecurityGroups.data, ports, service, region, results); + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/alibaba/ecs/openVNCServer.spec.js b/plugins/alibaba/ecs/openVNCServer.spec.js new file mode 100644 index 0000000000..246e33bc79 --- /dev/null +++ b/plugins/alibaba/ecs/openVNCServer.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +const openVNCServer = require('./openVNCServer'); + +const describeSecurityGroups = [ + { + "Description": "System created security group.", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "ServiceManaged": false, + "ResourceGroupId": "", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "CreationTime": "2021-04-30T09:57:23Z", + "SecurityGroupType": "normal", + "Tags": { + "Tag": [] + } + } +]; + +const describeSecurityGroupAttribute = [ + { + "Description": "System created security group.", + "RequestId": "B417712F-F2D9-4D84-9E14-53642866EC41", + "SecurityGroupName": "sg-rj998kwpxbxh3muao6nx", + "VpcId": "vpc-rj9vu86hdve3qr173ew17", + "SecurityGroupId": "sg-rj998kwpxbxh3muao6nx", + "Permissions": { + "Permission": [ + { + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "443/443", + "SourceCidrIp": "0.0.0.0/0", + "IpProtocol": "TCP" + } + ] + } + }, + { + "Description": "System created security group.", + "RequestId": "BCC3A7D9-93A5-44AA-85C1-A0C94A53DDBD", + "SecurityGroupName": "sg-0xijcm5n3s67cgnlklmi", + "VpcId": "vpc-0xitjib9awrnrv6i3sk9y", + "SecurityGroupId": "sg-0xijcm5n3s67cgnlklmi", + "Permissions": { + "Permission": [ + { + "SourceGroupId": "", + "Policy": "Accept", + "Description": "System created rule.", + "SourcePortRange": "", + "Priority": 100, + "CreateTime": "2021-04-29T22:40:41Z", + "DestPrefixListName": "", + "Ipv6SourceCidrIp": "", + "NicType": "intranet", + "DestGroupId": "", + "Direction": "ingress", + "SourceGroupName": "", + "PortRange": "5900/5900", + "DestGroupOwnerAccount": "", + "DestPrefixListId": "", + "SourceCidrIp": "0.0.0.0/0", + "SourcePrefixListName": "", + "IpProtocol": "TCP", + "DestCidrIp": "", + "DestGroupName": "", + "SourceGroupOwnerAccount": "", + "Ipv6DestCidrIp": "", + "SourcePrefixListId": "" + }, + ] + } + } +]; + +const createCache = (securityGroups, describeSecurityGroupAttribute, securityGroupsErr, describeSecurityGroupAttributeErr) => { + const securityGroupId = (securityGroups && securityGroups.length) ? securityGroups[0].SecurityGroupId : null; + return { + ecs:{ + DescribeSecurityGroups: { + 'cn-hangzhou': { + err: securityGroupsErr, + data: securityGroups + } + }, + DescribeSecurityGroupAttribute: { + 'cn-hangzhou': { + [securityGroupId]: { + err: describeSecurityGroupAttributeErr, + data: describeSecurityGroupAttribute + } + } + } + } + }; +}; + +describe('openVNCServer', function () { + describe('run', function () { + it('should PASS if no public open ports found', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[0]); + openVNCServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No public open ports found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should FAIL if security group has VNC Server TCP 5900 port open to public', function (done) { + const cache = createCache(describeSecurityGroups, describeSecurityGroupAttribute[1]); + openVNCServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has VNC Server:TCP:5900 open to 0.0.0.0/0'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if no security groups found', function (done) { + const cache = createCache([]); + openVNCServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No security groups found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNWON unable to describe security groups', function (done) { + const cache = createCache(null, { message: 'Unable to describe security groups'}); + openVNCServer.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to describe security groups'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }); +}); diff --git a/plugins/alibaba/ram/accessKeysRotation.js b/plugins/alibaba/ram/accessKeysRotation.js new file mode 100644 index 0000000000..936235dd8e --- /dev/null +++ b/plugins/alibaba/ram/accessKeysRotation.js @@ -0,0 +1,88 @@ +var helpers = require('../../../helpers/alibaba'); + +module.exports = { + title: 'Access Keys Rotation', + category: 'RAM', + description: 'Ensure that RAM user access keys are rotated after regular interval of time.', + more_info: 'Access keys should be rotated to avoid having them accidentally exposed.', + link: 'https://www.alibabacloud.com/help/doc-detail/152682.htm', + recommended_action: 'Rotate the access keys every desired number of days', + apis: ['RAM:ListUsers', 'RAM:ListAccessKeys', 'STS:GetCallerIdentity'], + settings: { + ram_access_keys_rotation_interval: { + name: 'RAM User Access Keys Rotation Interval', + description: 'Return a failing result when access keys exceed this number of days without being rotated', + regex: '^[1-9]{1}[0-9]{0,3}$', + default: '90' + } + }, + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + + var accessKeyRotationInterval = parseInt(settings.ram_access_keys_rotation_interval || this.settings.ram_access_keys_rotation_interval.default); + var region = helpers.defaultRegion(settings); + var accountId = helpers.addSource(cache, source, ['sts', 'GetCallerIdentity', region, 'data']); + var listUsers = helpers.addSource(cache, source, + ['ram', 'ListUsers', region]); + + if (!listUsers) return callback(null, results, source); + + if (listUsers.err || !listUsers.data) { + helpers.addResult(results, 3, + 'Unable to query RAM users' + helpers.addError(listUsers), region); + return callback(null, results, source); + } + + if (!listUsers.data.length) { + helpers.addResult(results, 0, 'No RAM users found', region); + return callback(null, results, source); + } + + for (var user of listUsers.data) { + if (!user.UserName) continue; + + var getAccessKey = helpers.addSource(cache, source, + ['ram', 'ListAccessKeys', region, user.UserName]); + + if (getAccessKey.err || !getAccessKey.data) { + helpers.addResult(results, 3, + 'Unable to query user access keys' + helpers.addError(getAccessKey), region); + continue; + } + + let resource = helpers.createArn('ram', accountId, 'user', user.UserName); + if (getAccessKey.data.AccessKeys && getAccessKey.data.AccessKeys.AccessKey && getAccessKey.data.AccessKeys.AccessKey.length) { + let activeKeyFound = false; + for (var accessKey of getAccessKey.data.AccessKeys.AccessKey) { + if (accessKey.Status && accessKey.Status == 'Active') { + activeKeyFound = true; + resource = resource + ':' + accessKey.AccessKeyId; + let createDate = accessKey.CreateDate; + var currentDate = new Date(); + var createDateFormat = new Date(createDate); + + var diffInDays = helpers.daysBetween(currentDate, createDateFormat); + if (diffInDays <= accessKeyRotationInterval) { + helpers.addResult(results, 0, + `RAM user access key was last rotated ${diffInDays} days ago which is equal to or less than ${accessKeyRotationInterval}`, region, resource); + } else { + helpers.addResult(results, 2, + `RAM user access key was last rotated ${diffInDays} days ago which is greater than ${accessKeyRotationInterval}`, region, resource); + } + } + } + if (!activeKeyFound) { + helpers.addResult(results, 0, + 'RAM user does not have any active access keys', region, resource); + } + } else { + helpers.addResult(results, 0, + 'RAM user does not have any access keys', region, resource); + } + } + + callback(null, results, source); + } +}; diff --git a/plugins/alibaba/ram/accessKeysRotation.spec.js b/plugins/alibaba/ram/accessKeysRotation.spec.js new file mode 100644 index 0000000000..7007a61bfd --- /dev/null +++ b/plugins/alibaba/ram/accessKeysRotation.spec.js @@ -0,0 +1,141 @@ +var expect = require('chai').expect; +var helpers = require('../../../helpers/alibaba'); +var accessKeysRotation = require('./accessKeysRotation') + +var passDate = new Date(); +passDate.setMonth(passDate.getMonth() - 2); +var failDate = new Date(); +failDate.setMonth(failDate.getMonth() - 7); + +const listUsers = [ + { + "UserName": "aqua", + "UserId": "254529020129829608", + }, + { + "UserName": "cloudsploit", + "UserId": "283806919721151694", + } +]; + +const getUserLoginProfile = [ + { + AccessKeys: { + AccessKey: [ + { + Status: "Active", + AccessKeyId: "LTAI5tD6ekrSssrWq5rNa4JQ", + CreateDate: failDate, + }, + ], + }, + }, + { + AccessKeys: { + AccessKey: [ + { + Status: "Active", + AccessKeyId: "LTAI5tD6ekrSssrWq5rNa4JQ", + CreateDate: passDate, + }, + ], + }, + }, + { + AccessKeys: { + AccessKey: [], + }, + } +]; + +const createCache = (users, accessKeys, accessKeysError, error) => { + let userName = (users && users.length) ? users[0].UserName : null; + return { + ram: { + ListUsers: { + 'cn-hangzhou': { + data: users, + err: error + } + }, + ListAccessKeys: { + 'cn-hangzhou': { + [userName]: { + data: accessKeys, + err: accessKeysError + } + } + }, + } + } +} + +describe('accessKeysRotation', function () { + describe('run', function () { + it('should FAIL if RAM user access keys are not rotated every 90 days or less', function (done) { + const cache = createCache([listUsers[0]], getUserLoginProfile[0], null, null); + accessKeysRotation.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('which is greater than'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if RAM user access keys are not rotated every 90 days or less', function (done) { + const cache = createCache([listUsers[0]], getUserLoginProfile[1], null, null); + accessKeysRotation.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('which is equal to or less than'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if RAM user does not have any access keys', function (done) { + const cache = createCache([listUsers[1]], getUserLoginProfile[2], null, null); + accessKeysRotation.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('RAM user does not have any access keys'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should PASS if No RAM users found', function (done) { + const cache = createCache([]); + accessKeysRotation.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No RAM users found'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNOWN if unable to query user access keys', function (done) { + const cache = createCache([listUsers[0]], null, [], null); + accessKeysRotation.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to query user access keys'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + + it('should UNKNOWN if unable to query RAM users', function (done) { + const cache = createCache(null, null, null, null); + accessKeysRotation.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to query RAM users'); + expect(results[0].region).to.equal('cn-hangzhou'); + done(); + }); + }); + }) +}) \ No newline at end of file diff --git a/plugins/alibaba/ram/passwordExpiry.js b/plugins/alibaba/ram/passwordExpiry.js index a91c5d0d87..29a0a77e52 100644 --- a/plugins/alibaba/ram/passwordExpiry.js +++ b/plugins/alibaba/ram/passwordExpiry.js @@ -3,11 +3,19 @@ var helpers = require('../../../helpers/alibaba'); module.exports = { title: 'Password Expiry', category: 'RAM', - description: 'Ensure that RAM password security settings require password to be expired after 90 days.', + description: 'Ensure that RAM password security settings require password to be expired after set number of days.', more_info: 'A strong password policy enforces minimum length, expiration, reuse, and symbol usage.', link: 'https://www.alibabacloud.com/help/doc-detail/116413.htm', - recommended_action: 'Update the password security settings to require password to be expired after 90 days.', + recommended_action: 'Update the password security settings to require password to be expired after set number of days.', apis: ['RAM:GetPasswordPolicy'], + settings: { + ram_password_expiry: { + name: 'RAM User Password Expiry', + description: 'Maximum number of days after which RAM user password should be expired', + regex: '^[1-9]{1}[0-9]{0,3}$', + default: '90' + } + }, run: function(cache, settings, callback) { var results = []; @@ -17,6 +25,8 @@ module.exports = { var getPasswordPolicy = helpers.addSource(cache, source, ['ram', 'GetPasswordPolicy', region]); + var ramPasswordExpiry = parseInt(settings.ram_password_expiry || this.settings.ram_password_expiry.default); + if (!getPasswordPolicy) return callback(null, results, source); if (getPasswordPolicy.err || !getPasswordPolicy.data || !Object.keys(getPasswordPolicy.data).length) { @@ -25,14 +35,12 @@ module.exports = { return callback(null, results, source); } - if (getPasswordPolicy.data.MaxPasswordAge && - getPasswordPolicy.data.MaxPasswordAge > 0 && - getPasswordPolicy.data.MaxPasswordAge <= 90) { + if (getPasswordPolicy.data.MaxPasswordAge <= ramPasswordExpiry) { helpers.addResult(results, 0, - 'RAM password security policy requires password to be expired after 90 days', region); + `RAM password security policy requires password to be expired after ${getPasswordPolicy.data.MaxPasswordAge} days which is equal to or less than desired limit of ${ramPasswordExpiry}`, region); } else { helpers.addResult(results, 2, - 'RAM password security policy does not require password to be expired after 90 days', region); + `RAM password security policy requires password to be expired after ${getPasswordPolicy.data.MaxPasswordAge} days which is greater than desired limit of ${ramPasswordExpiry}`, region); } callback(null, results, source); diff --git a/plugins/alibaba/ram/passwordExpiry.spec.js b/plugins/alibaba/ram/passwordExpiry.spec.js index 7bb9eedfc6..f66686d502 100644 --- a/plugins/alibaba/ram/passwordExpiry.spec.js +++ b/plugins/alibaba/ram/passwordExpiry.spec.js @@ -46,18 +46,18 @@ describe('passwordExpiry', function () { passwordExpiry.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('RAM password security policy does not require password to be expired after 90 days'); + expect(results[0].message).to.include('RAM password security policy requires password to be expired after 91 days which is greater than desired limit of 90'); expect(results[0].region).to.equal('cn-hangzhou'); done(); }); }); - it('should PASS if RAM password security policy requires password to be expired after 90 days', function (done) { + it('should PASS if RAM password security policy requires password to be expired after set days', function (done) { const cache = createCache(getPasswordPolicy[1]); - passwordExpiry.run(cache, {}, (err, results) => { + passwordExpiry.run(cache, { ram_password_expiry: '100' }, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('RAM password security policy requires password to be expired after 90 days'); + expect(results[0].message).to.include('RAM password security policy requires password to be expired after 10 days which is equal to or less than desired limit of 100'); expect(results[0].region).to.equal('cn-hangzhou'); done(); }); diff --git a/plugins/alibaba/ram/passwordMinLength.js b/plugins/alibaba/ram/passwordMinLength.js index 4ab4ae5426..03b3a0b679 100644 --- a/plugins/alibaba/ram/passwordMinLength.js +++ b/plugins/alibaba/ram/passwordMinLength.js @@ -12,11 +12,22 @@ module.exports = { pci: 'PCI requires a strong password policy. Setting Identity password ' + 'requirements enforces this policy.' }, + settings: { + ram_password_min_length: { + name: 'RAM User Password Minimum Length', + description: 'Minimum password length required for RAM user login passwords. Should be 14 or above', + regex: '^1[4-9]|[2-9]{2,3}$', + default: '14' + } + }, + run: function(cache, settings, callback) { var results = []; var source = {}; var region = helpers.defaultRegion(settings); + var minPasswordLength = parseInt(settings.ram_password_min_length || this.settings.ram_password_min_length.default); + var getPasswordPolicy = helpers.addSource(cache, source, ['ram', 'GetPasswordPolicy', region]); @@ -28,12 +39,12 @@ module.exports = { return callback(null, results, source); } - if (getPasswordPolicy.data.MinimumPasswordLength >= 14) { + if (getPasswordPolicy.data.MinimumPasswordLength >= minPasswordLength) { helpers.addResult(results, 0, - 'RAM password security policy require minimum length of 14', region); + `RAM password security policy requires minimum length of ${getPasswordPolicy.data.MinimumPasswordLength} which is equal to or greater than desired limit of ${minPasswordLength}`, region); } else { helpers.addResult(results, 2, - 'RAM password security policy does not require minimum length of 14', region); + `RAM password security policy requires minimum length of ${getPasswordPolicy.data.MinimumPasswordLength} which is less than desired limit of ${minPasswordLength}`, region); } callback(null, results, source); diff --git a/plugins/alibaba/ram/passwordMinLength.spec.js b/plugins/alibaba/ram/passwordMinLength.spec.js index 8e14f364c2..3ecf1c2150 100644 --- a/plugins/alibaba/ram/passwordMinLength.spec.js +++ b/plugins/alibaba/ram/passwordMinLength.spec.js @@ -43,10 +43,10 @@ describe('passwordMinLength', function () { describe('run', function () { it('should FAIL if RAM password security policy does not require minimum length of 14 or greater', function (done) { const cache = createCache(getPasswordPolicy[1]); - passwordMinLength.run(cache, {}, (err, results) => { + passwordMinLength.run(cache, { ram_password_min_length: '15' }, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('RAM password security policy does not require minimum length of 14'); + expect(results[0].message).to.include('RAM password security policy requires minimum length of 8 which is less than desired limit of 15'); expect(results[0].region).to.equal('cn-hangzhou'); done(); }); @@ -57,7 +57,7 @@ describe('passwordMinLength', function () { passwordMinLength.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('RAM password security policy require minimum length of 14'); + expect(results[0].message).to.include('RAM password security policy requires minimum length of 15 which is equal to or greater than desired limit of 14'); expect(results[0].region).to.equal('cn-hangzhou'); done(); }); diff --git a/plugins/alibaba/ram/passwordNoReuse.js b/plugins/alibaba/ram/passwordNoReuse.js index 69125ee1db..8782f83c34 100644 --- a/plugins/alibaba/ram/passwordNoReuse.js +++ b/plugins/alibaba/ram/passwordNoReuse.js @@ -3,17 +3,27 @@ var helpers = require('../../../helpers/alibaba'); module.exports = { title: 'Password No Reuse', category: 'RAM', - description: 'Ensure that RAM password security settings are set to prevent reusing 5 old passwords.', + description: 'Ensure that RAM password security settings are set to prevent reusing desired number of previous passwords.', more_info: 'A strong password policy enforces minimum length, expiration, reuse, and symbol usage.', link: 'https://www.alibabacloud.com/help/doc-detail/116413.htm', - recommended_action: 'Update the password security settings to prevent reusing 5 old passwords.', + recommended_action: 'Update the password security settings to prevent reusing desired number of previous passwords.', apis: ['RAM:GetPasswordPolicy'], + settings: { + ram_password_no_reuse_limit: { + name: 'RAM User Password No Reuse Limit', + description: 'Maximum number of previous user passwords which should not be reused', + regex: '^[5-9]|[0-9]{2,3}$', + default: '5' + } + }, run: function(cache, settings, callback) { var results = []; var source = {}; var region = helpers.defaultRegion(settings); + var ramPasswordReuse = parseInt(settings.ram_password_no_reuse_limit || this.settings.ram_password_no_reuse_limit.default); + var getPasswordPolicy = helpers.addSource(cache, source, ['ram', 'GetPasswordPolicy', region]); @@ -25,12 +35,12 @@ module.exports = { return callback(null, results, source); } - if (getPasswordPolicy.data.PasswordReusePrevention >= 5) { + if (getPasswordPolicy.data.PasswordReusePrevention >= ramPasswordReuse) { helpers.addResult(results, 0, - 'RAM password security policy requires to prevent reusing previous 5 or more passwords', region); + `RAM password security policy requires to prevent reusing previous ${getPasswordPolicy.data.PasswordReusePrevention} passwords which is equal to or greater than desired limit of ${ramPasswordReuse}`, region); } else { helpers.addResult(results, 2, - 'RAM password security policy does not require to prevent reusing previous 5 passwords', region); + `RAM password security policy requires to prevent reusing previous ${getPasswordPolicy.data.PasswordReusePrevention} passwords which is less than desired limit of ${ramPasswordReuse}`, region); } callback(null, results, source); diff --git a/plugins/alibaba/ram/passwordNoReuse.spec.js b/plugins/alibaba/ram/passwordNoReuse.spec.js index f354a46f87..0d1f9f2bcb 100644 --- a/plugins/alibaba/ram/passwordNoReuse.spec.js +++ b/plugins/alibaba/ram/passwordNoReuse.spec.js @@ -19,7 +19,7 @@ const getPasswordPolicy = [ RequireNumbers:false, MaxLoginAttemps:0, MaxPasswordAge:0, - PasswordReusePrevention:0, + PasswordReusePrevention:3, HardExpiry:false, RequireUppercaseCharacters:true, RequireSymbols:false @@ -46,7 +46,7 @@ describe('passwordNoReuse', function () { passwordNoReuse.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('RAM password security policy does not require to prevent reusing previous 5 passwords'); + expect(results[0].message).to.include('RAM password security policy requires to prevent reusing previous 3 passwords which is less than desired limit of 5'); expect(results[0].region).to.equal('cn-hangzhou'); done(); }); @@ -57,7 +57,7 @@ describe('passwordNoReuse', function () { passwordNoReuse.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('RAM password security policy requires to prevent reusing previous 5 or more passwords'); + expect(results[0].message).to.include('RAM password security policy requires to prevent reusing previous 5 passwords which is equal to or greater than desired limit of 5'); expect(results[0].region).to.equal('cn-hangzhou'); done(); }); diff --git a/plugins/azure/appservice/automatedBackupsConfigured.js b/plugins/azure/appservice/appserviceAutomatedBackups.js similarity index 74% rename from plugins/azure/appservice/automatedBackupsConfigured.js rename to plugins/azure/appservice/appserviceAutomatedBackups.js index 6d502a6b58..a04fdc9130 100644 --- a/plugins/azure/appservice/automatedBackupsConfigured.js +++ b/plugins/azure/appservice/appserviceAutomatedBackups.js @@ -35,22 +35,20 @@ module.exports = { } async.each(webApps.data, function(webApp, scb) { - if (webApp && webApp.kind === 'functionapp') { + if (webApp.kind && webApp.kind === 'functionapp') { helpers.addResult(results, 0, 'Backups can not be configured for the function App', location, webApp.id); return scb(); - } else { - const backupConfig = helpers.addSource( - cache, source, ['webApps', 'listBackupConfig', location, webApp.id] - ); + } - if (!backupConfig || backupConfig.err || !backupConfig.data) { - helpers.addResult(results, 2, 'Automated Backups are not configured for the webApp', location, webApp.id); - return scb(); - } + const backupConfig = helpers.addSource( + cache, source, ['webApps', 'listBackupConfig', location, webApp.id] + ); - if (backupConfig.data) { - helpers.addResult(results, 0, 'Automated Backups are configured for the webApp', location, webApp.id); - } + if (!backupConfig || backupConfig.err || !backupConfig.data) { + helpers.addResult(results, 2, 'Automated Backups are not configured for the webApp', location, webApp.id); + return scb(); + } else { + helpers.addResult(results, 0, 'Automated Backups are configured for the webApp', location, webApp.id); } scb(); diff --git a/plugins/azure/appservice/automatedBackupsConfigured.spec.js b/plugins/azure/appservice/appserviceAutomatedBackups.spec.js similarity index 89% rename from plugins/azure/appservice/automatedBackupsConfigured.spec.js rename to plugins/azure/appservice/appserviceAutomatedBackups.spec.js index 54e10aa723..c77f2437d6 100644 --- a/plugins/azure/appservice/automatedBackupsConfigured.spec.js +++ b/plugins/azure/appservice/appserviceAutomatedBackups.spec.js @@ -1,5 +1,5 @@ var expect = require('chai').expect; -var automatedBackupsConfigured = require('./automatedBackupsConfigured'); +var appserviceAutomatedBackups = require('./appserviceAutomatedBackups'); const webApps = [ { @@ -55,11 +55,11 @@ const createCache = (webApps, backupConfig) => { }; }; -describe('automatedBackupsConfigured', function() { +describe('appserviceAutomatedBackups', function() { describe('run', function() { it('should give passing result if no web apps', function(done) { const cache = createCache([]); - automatedBackupsConfigured.run(cache, {}, (err, results) => { + appserviceAutomatedBackups.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); expect(results[0].message).to.include('No existing Web Apps found'); @@ -70,7 +70,7 @@ describe('automatedBackupsConfigured', function() { it('should give unknown result if unable to query for web apps', function(done) { const cache = createCache(); - automatedBackupsConfigured.run(cache, {}, (err, results) => { + appserviceAutomatedBackups.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(3); expect(results[0].message).to.include('Unable to query for Web Apps:'); @@ -81,7 +81,7 @@ describe('automatedBackupsConfigured', function() { it('should give passing result if app is a function app', function(done) { const cache = createCache([webApps[1]]); - automatedBackupsConfigured.run(cache, {}, (err, results) => { + appserviceAutomatedBackups.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); expect(results[0].message).to.include('Backups can not be configured for the function App'); @@ -94,7 +94,7 @@ describe('automatedBackupsConfigured', function() { const cache = createCache([webApps[0]], { err: 'Unknown error occurred while calling the Azure API' }); - automatedBackupsConfigured.run(cache, {}, (err, results) => { + appserviceAutomatedBackups.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); expect(results[0].message).to.include('Automated Backups are not configured for the webApp'); @@ -105,7 +105,7 @@ describe('automatedBackupsConfigured', function() { it('should give failing result if no app config found', function(done) { const cache = createCache([webApps[0]], {}); - automatedBackupsConfigured.run(cache, {}, (err, results) => { + appserviceAutomatedBackups.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); expect(results[0].message).to.include('Automated Backups are not configured for the webApp'); @@ -118,7 +118,7 @@ describe('automatedBackupsConfigured', function() { const cache = createCache([webApps[0]], { 'data': backupConfig }); - automatedBackupsConfigured.run(cache, {}, (err, results) => { + appserviceAutomatedBackups.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); expect(results[0].message).to.include('Automated Backups are configured for the webApp'); diff --git a/plugins/google/sql/postgresqlLogMinDuration.js b/plugins/google/sql/postgresqlLogMinDuration.js new file mode 100644 index 0000000000..e6cece796e --- /dev/null +++ b/plugins/google/sql/postgresqlLogMinDuration.js @@ -0,0 +1,68 @@ +var async = require('async'); +var helpers = require('../../../helpers/google'); + +module.exports = { + title: 'PostgreSQL Log Min Duration Statement', + category: 'SQL', + description: 'Ensures SQL instances for PostgreSQL type have log min duration statement flag disabled.', + more_info: 'SQL instance for PostgreSQL databases provides log_min_duration_statement flag. It is used to log the duration of every completed statement. This should always be disabled as there can be sensitive information as well that should not be recorded in the logs.', + link: 'https://cloud.google.com/sql/docs/postgres/flags', + recommended_action: 'Ensure that log_min_duration_statement flag is disabled for all PostgreSQL instances.', + apis: ['instances:sql:list'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(); + + async.each(regions.instances.sql, function(region, rcb) { + let sqlInstances = helpers.addSource( + cache, source, ['instances', 'sql', 'list', region]); + + if (!sqlInstances) return rcb(); + + if (sqlInstances.err || !sqlInstances.data) { + helpers.addResult(results, 3, 'Unable to query SQL instances: ' + helpers.addError(sqlInstances), region); + return rcb(); + } + + if (!sqlInstances.data.length) { + helpers.addResult(results, 0, 'No SQL instances found', region); + return rcb(); + } + + sqlInstances.data.forEach(sqlInstance => { + if (sqlInstance.databaseVersion && !sqlInstance.databaseVersion.toLowerCase().includes('postgres')) { + helpers.addResult(results, 0, + 'SQL instance database type is not of PostgreSQL type', region, sqlInstance.name); + return; + } + + if (sqlInstance.instanceType != "READ_REPLICA_INSTANCE" && + sqlInstance.settings && + sqlInstance.settings.databaseFlags && + sqlInstance.settings.databaseFlags.length) { + let found = sqlInstance.settings.databaseFlags.find(flag => flag.name && flag.name == 'log_min_duration_statement' && + flag.value && flag.value == '-1'); + + if (found) { + helpers.addResult(results, 0, + 'SQL instance does not have log_min_duration_statement flag enabled', region, sqlInstance.name); + } else { + helpers.addResult(results, 2, + 'SQL instance have log_min_duration_statement flag enabled', region, sqlInstance.name); + } + } else if (sqlInstance.instanceType == "READ_REPLICA_INSTANCE") { + } else { + helpers.addResult(results, 0, + 'SQL instance does not have log_min_duration_statement flag enabled', region, sqlInstance.name); + } + }); + + rcb(); + }, function() { + // Global checking goes here + callback(null, results, source); + }); + } +} diff --git a/plugins/google/sql/postgresqlLogMinDuration.spec.js b/plugins/google/sql/postgresqlLogMinDuration.spec.js new file mode 100644 index 0000000000..a619774148 --- /dev/null +++ b/plugins/google/sql/postgresqlLogMinDuration.spec.js @@ -0,0 +1,180 @@ +var assert = require('assert'); +var expect = require('chai').expect; +var plugin = require('./postgresqlLogMinDuration'); + +const createCache = (err, data) => { + return { + instances: { + sql: { + list: { + 'global': { + err: err, + data: data + } + } + } + } + } +}; + +describe('postgresqlLogMinDuration', function () { + describe('run', function () { + it('should give unknown result if a sql instance error is passed or no data is present', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to query SQL instances'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + ['error'], + null, + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if no sql instances are found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No SQL instances found'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [], + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if sql instance database type is not of postgreSQL type', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance database type is not of PostgreSQL type'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + name: "testing-instance", + databaseVersion: "MYSQL_5_7", + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give passing result if sql instances does not have log_min_duration_statement flag enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance does not have log_min_duration_statement flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_min_duration_statement", + value: '-1', + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give failing result if sql instances have log_min_duration_statement flag enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance have log_min_duration_statement flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_min_duration_statement", + value: '0', + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give failing result if sql instances have log_min_duration_statement flag enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance have log_min_duration_statement flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_checkpoints", + value: "on", + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give passing result if sql instances does not have log_min_duration_statement flag', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance does not have log_min_duration_statement flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [] + } + }], + ); + + plugin.run(cache, {}, callback); + }); + }) +}) \ No newline at end of file diff --git a/plugins/google/sql/postgresqlLogMinError.js b/plugins/google/sql/postgresqlLogMinError.js new file mode 100644 index 0000000000..a8cd758755 --- /dev/null +++ b/plugins/google/sql/postgresqlLogMinError.js @@ -0,0 +1,77 @@ +var async = require('async'); +var helpers = require('../../../helpers/google'); + +module.exports = { + title: 'PostgreSQL Log Min Error Statement', + category: 'SQL', + description: 'Ensures SQL instances for PostgreSQL type have log min error statement flag set to Error.', + more_info: 'SQL instance for PostgreSQL databases provides log_min_error_statement flag. It is used to mention/tag that the error messages. Setting it to Error value will help to find the error messages appropriately.', + link: 'https://cloud.google.com/sql/docs/postgres/flags', + recommended_action: 'Ensure that log_min_error_statement flag is set to Error for all PostgreSQL instances.', + apis: ['instances:sql:list'], + settings: { + log_min_error_statement: { + name: 'Log Min Error Statement', + description: 'Return a passing result if the flag value is used from the setting list.', + regex: '^(LOG|FATAL|PANIC)$', + default: 'ERROR' + } + }, + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(); + + var log_min_error_statement = settings.log_min_error_statement || this.settings.log_min_error_statement.default; + async.each(regions.instances.sql, function(region, rcb) { + let sqlInstances = helpers.addSource( + cache, source, ['instances', 'sql', 'list', region]); + + if (!sqlInstances) return rcb(); + + if (sqlInstances.err || !sqlInstances.data) { + helpers.addResult(results, 3, 'Unable to query SQL instances: ' + helpers.addError(sqlInstances), region); + return rcb(); + } + + if (!sqlInstances.data.length) { + helpers.addResult(results, 0, 'No SQL instances found', region); + return rcb(); + } + + sqlInstances.data.forEach(sqlInstance => { + if (sqlInstance.databaseVersion && !sqlInstance.databaseVersion.toLowerCase().includes('postgres')) { + helpers.addResult(results, 0, + 'SQL instance database type is not of PostgreSQL type', region, sqlInstance.name); + return; + } + + if (sqlInstance.instanceType != "READ_REPLICA_INSTANCE" && + sqlInstance.settings && + sqlInstance.settings.databaseFlags && + sqlInstance.settings.databaseFlags.length) { + let found = sqlInstance.settings.databaseFlags.find(flag => flag.name && flag.name == 'log_min_error_statement' && + flag.value && flag.value == log_min_error_statement); + + if (found) { + helpers.addResult(results, 0, + 'SQL instance have log_min_error_statement flag set to Error', region, sqlInstance.name); + } else { + helpers.addResult(results, 2, + 'SQL instance does not have log_min_error_statement flag set to Error', region, sqlInstance.name); + } + } else if (sqlInstance.instanceType == "READ_REPLICA_INSTANCE") { + } else { + helpers.addResult(results, 2, + 'SQL instance does not have log_min_error_statement flag set to Error', region, sqlInstance.name); + } + }); + + rcb(); + }, function() { + // Global checking goes here + callback(null, results, source); + }); + } +} \ No newline at end of file diff --git a/plugins/google/sql/postgresqlLogMinError.spec.js b/plugins/google/sql/postgresqlLogMinError.spec.js new file mode 100644 index 0000000000..9bb9a2015c --- /dev/null +++ b/plugins/google/sql/postgresqlLogMinError.spec.js @@ -0,0 +1,209 @@ +var assert = require('assert'); +var expect = require('chai').expect; +var plugin = require('./postgresqlLogMinError'); + +const createCache = (err, data) => { + return { + instances: { + sql: { + list: { + 'global': { + err: err, + data: data + } + } + } + } + } +}; + +describe('postgresqlLogMinError', function () { + describe('run', function () { + it('should give unknown result if a sql instance error is passed or no data is present', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to query SQL instances'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + ['error'], + null, + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if no sql instances are found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No SQL instances found'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [], + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if sql instance database type is not of postgreSQL type', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance database type is not of PostgreSQL type'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + name: "testing-instance", + databaseVersion: "MYSQL_5_7", + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give passing result if sql instances have log_min_error_statement flag set to Error', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance have log_min_error_statement flag set to Error'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_min_error_statement", + value: 'ERROR', + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if sql instances have log_min_error_statement flag set to Error or Stricter', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance have log_min_error_statement flag set to Error'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_min_error_statement", + value: 'FATAL', + }, + ]} + }], + ); + + plugin.run(cache, { log_min_error_statement: 'FATAL' }, callback); + }); + + it('should give failing result if sql instances does not have log_min_error_statement flag set to Error', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance does not have log_min_error_statement flag set to Error'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_min_error_statement", + value: 'WARNING', + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give failing result if sql instances does not have log_min_error_statement flag set to Error', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance does not have log_min_error_statement flag set to Error'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_checkpoints", + value: "on", + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give failing result if sql instances does not have log_min_error_statement flag set to Error', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance does not have log_min_error_statement flag set to Error'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [] + } + }], + ); + + plugin.run(cache, {}, callback); + }); + }) +}) \ No newline at end of file diff --git a/plugins/google/sql/postgresqlLogTempFiles.js b/plugins/google/sql/postgresqlLogTempFiles.js new file mode 100644 index 0000000000..2a0904f2c9 --- /dev/null +++ b/plugins/google/sql/postgresqlLogTempFiles.js @@ -0,0 +1,68 @@ +var async = require('async'); +var helpers = require('../../../helpers/google'); + +module.exports = { + title: 'PostgreSQL Log Temp Files', + category: 'SQL', + description: 'Ensures SQL instances for PostgreSQL type have log temp files flag enabled.', + more_info: 'SQL instance for PostgreSQL databases provides log_temp_files flag. It is used to log the temporary files name and size. It is not enabled by default. Enabling it will make sure to log names and sizes of all the temporary files that were created during any operation(sort, hashes, query_results etc).', + link: 'https://cloud.google.com/sql/docs/postgres/flags', + recommended_action: 'Ensure that log_temp_files flag is enabled for all PostgreSQL instances.', + apis: ['instances:sql:list'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(); + + async.each(regions.instances.sql, function(region, rcb) { + let sqlInstances = helpers.addSource( + cache, source, ['instances', 'sql', 'list', region]); + + if (!sqlInstances) return rcb(); + + if (sqlInstances.err || !sqlInstances.data) { + helpers.addResult(results, 3, 'Unable to query SQL instances: ' + helpers.addError(sqlInstances), region); + return rcb(); + } + + if (!sqlInstances.data.length) { + helpers.addResult(results, 0, 'No SQL instances found', region); + return rcb(); + } + + sqlInstances.data.forEach(sqlInstance => { + if (sqlInstance.databaseVersion && !sqlInstance.databaseVersion.toLowerCase().includes('postgres')) { + helpers.addResult(results, 0, + 'SQL instance database type is not of PostgreSQL type', region, sqlInstance.name); + return; + } + + if (sqlInstance.instanceType != "READ_REPLICA_INSTANCE" && + sqlInstance.settings && + sqlInstance.settings.databaseFlags && + sqlInstance.settings.databaseFlags.length) { + let found = sqlInstance.settings.databaseFlags.find(flag => flag.name && flag.name == 'log_temp_files' && + flag.value && flag.value == '0'); + + if (found) { + helpers.addResult(results, 0, + 'SQL instance have log_temp_files flag enabled', region, sqlInstance.name); + } else { + helpers.addResult(results, 2, + 'SQL instance does not have log_temp_files flag enabled', region, sqlInstance.name); + } + } else if (sqlInstance.instanceType == "READ_REPLICA_INSTANCE") { + } else { + helpers.addResult(results, 2, + 'SQL instance does not have log_temp_files flag enabled', region, sqlInstance.name); + } + }); + + rcb(); + }, function() { + // Global checking goes here + callback(null, results, source); + }); + } +} \ No newline at end of file diff --git a/plugins/google/sql/postgresqlLogTempFiles.spec.js b/plugins/google/sql/postgresqlLogTempFiles.spec.js new file mode 100644 index 0000000000..12bce9a436 --- /dev/null +++ b/plugins/google/sql/postgresqlLogTempFiles.spec.js @@ -0,0 +1,180 @@ +var assert = require('assert'); +var expect = require('chai').expect; +var plugin = require('./postgresqlLogTempFiles'); + +const createCache = (err, data) => { + return { + instances: { + sql: { + list: { + 'global': { + err: err, + data: data + } + } + } + } + } +}; + +describe('postgresqlLogTempFiles', function () { + describe('run', function () { + it('should give unknown result if a sql instance error is passed or no data is present', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to query SQL instances'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + ['error'], + null, + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if no sql instances are found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No SQL instances found'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [], + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if sql instance database type is not of postgreSQL type', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance database type is not of PostgreSQL type'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + name: "testing-instance", + databaseVersion: "MYSQL_5_7", + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give passing result if sql instances have log_temp_files flag enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('SQL instance have log_temp_files flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_temp_files", + value: '0', + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give failing result if sql instances does not have log_temp_files flag enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance does not have log_temp_files flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_temp_files", + value: '-1', + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give failing result if sql instances does not have log_temp_files flag enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance does not have log_temp_files flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [ + { + name: "log_checkpoints", + value: "on", + }, + ]} + }], + ); + + plugin.run(cache, {}, callback); + }); + it('should give failing result if sql instances does not have log_temp_files flag enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('SQL instance does not have log_temp_files flag enabled'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [{ + instanceType: "CLOUD_SQL_INSTANCE", + name: "testing-instance", + databaseVersion: "POSTGRES_13", + settings: { + databaseFlags: [] + } + }], + ); + + plugin.run(cache, {}, callback); + }); + }) +}) \ No newline at end of file diff --git a/plugins/oracle/identity/emptyGroups.js b/plugins/oracle/identity/emptyGroups.js index 863c15e5cf..a0dd86ee34 100644 --- a/plugins/oracle/identity/emptyGroups.js +++ b/plugins/oracle/identity/emptyGroups.js @@ -50,7 +50,7 @@ module.exports = { }); if (users && users.length) { - helpers.addResult(results, 0, `Group: ${group.name} contains ' + users.length + ' user(s)`, defaultRegion, group.id); + helpers.addResult(results, 0, `Group: ${group.name} contains ${users.length} user(s)`, defaultRegion, group.id); } else { helpers.addResult(results, 1, `Group: ${group.name} does not contain any users`, defaultRegion,group.id) } diff --git a/plugins/oracle/identity/emptyGroups.spec.js b/plugins/oracle/identity/emptyGroups.spec.js new file mode 100644 index 0000000000..027ac9667e --- /dev/null +++ b/plugins/oracle/identity/emptyGroups.spec.js @@ -0,0 +1,126 @@ +var assert = require('assert'); +var expect = require('chai').expect; +var plugin = require('./emptyGroups'); + +const userGroups = [ + { + "userId": 'ocid1.user.oc1..11111111111111222222222222222333333333333', + "groupId": 'ocid1.group.oc1..aaaaaaaa5y47111111222222333333', + "id": 'ocid1.groupmembership.oc1..111111111222222222223333333333333' + }, + { + "userId": 'ocid1.user.oc1..1111111111111111112222222222222222333333333333', + "groupId": 'ocid1.group.oc1..aaaaaaaa5y47111111222222333333', + "id": 'ocid1.groupmembership.oc1..1111111111111222222222222222233333333333333' + }, + { + "userId": 'ocid1.user.oc1..111111111111111122222222222222222333333333333333', + "groupId": 'ocid1.group.oc1..aaaaaaaa5y47111111222222333333', + "id": 'ocid1.groupmembership.oc1..1111111111111112222222222222222222333333333333' + } +]; + +const groups = [ + { + "id": 'ocid1.group.oc1..aaaaaaaa5y47111111222222333333', + "name": 'Administrators' + }, + { + "id": 'ocid1.group.oc1..111111111111111222222222222223333333333333333', + "name": 'securityAudit' + } +] + +const createCache = (groups, userGroups, groupsErr, userGroupsErr) => { + return { + group: { + list: { + 'us-ashburn-1': { + data: groups, + err: groupsErr + } + } + }, + userGroupMembership: { + list: { + 'us-ashburn-1': { + data: userGroups, + err: userGroupsErr + } + } + } + } +}; + +describe('emptyGroups', function () { + describe('run', function () { + it('should give unknown result if unable to query user groups', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query user groups') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + [{}], + [{}], + {err: 'error'} + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no groups found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No groups found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + [], + [] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give warn result if group does not contain any users', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('does not contain any users') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + [groups[1]], + userGroups + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if group contains user(s)', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('contains 3 user(s)') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + [groups[0]], + userGroups + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/identity/minPasswordLength.js b/plugins/oracle/identity/minPasswordLength.js index bce5b6598b..2e1948d9a1 100644 --- a/plugins/oracle/identity/minPasswordLength.js +++ b/plugins/oracle/identity/minPasswordLength.js @@ -42,7 +42,7 @@ module.exports = { passwordPolicy.minimumPasswordLength) { if (passwordPolicy.minimumPasswordLength >= 14) { helpers.addResult(results, 0, 'Minimum password length of: ' + passwordPolicy.minimumPasswordLength + ' is suitable', 'global', authenticationPolicy.data.compartmentId); - } else if (passwordPolicy && + } else if (passwordPolicy && passwordPolicy.minimumPasswordLength && passwordPolicy.minimumPasswordLength < 10) { helpers.addResult(results, 2, 'Minimum password length of: ' + passwordPolicy.minimumPasswordLength + ' is less than the recommended 14 characters', 'global', authenticationPolicy.data.compartmentId); diff --git a/plugins/oracle/identity/minPasswordLength.spec.js b/plugins/oracle/identity/minPasswordLength.spec.js new file mode 100644 index 0000000000..459f0a1994 --- /dev/null +++ b/plugins/oracle/identity/minPasswordLength.spec.js @@ -0,0 +1,176 @@ +var expect = require('chai').expect; +var plugin = require('./minPasswordLength'); + +const authenticationPolicy = [ + { + "passwordPolicy": { + "minimumPasswordLength": 15, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + }, + { + "passwordPolicy": { + "minimumPasswordLength": 12, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + }, + { + "passwordPolicy": { + "minimumPasswordLength": 8, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + }, + { + "passwordPolicy": { + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + authenticationPolicy: { + get: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('minPasswordLength', function () { + describe('run', function () { + it('should give unknown result if unable to query for password policy status', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for password policy status') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + {err: 'error'}, + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no password policies found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No password policies found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give failing result if password policy does not require a minimum password length', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(2) + expect(results[0].message).to.include('Password policy does not require a minimum password length') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[3]] + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if Minimum password length is suitable', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('is suitable') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[0]] + ); + + plugin.run(cache, {}, callback); + }); + + it('should give warn result if Minimum password length is less than the recommended 14 characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('is less than the recommended 14 characters') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[1]] + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/identity/passwordRequiresLowercase.spec.js b/plugins/oracle/identity/passwordRequiresLowercase.spec.js new file mode 100644 index 0000000000..3b9accf076 --- /dev/null +++ b/plugins/oracle/identity/passwordRequiresLowercase.spec.js @@ -0,0 +1,138 @@ +var expect = require('chai').expect; +var plugin = require('./passwordRequiresLowercase'); + +const authenticationPolicy = [ + { + "passwordPolicy": { + "minimumPasswordLength": 15, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + }, + { + "passwordPolicy": { + "minimumPasswordLength": 12, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": false, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + authenticationPolicy: { + get: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('passwordRequiresLowercase', function () { + describe('run', function () { + it('should give unknown result if unable to query for password policy status', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for password policy status') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + {err: 'error'}, + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no password policies found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No password policies found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if password policy requires lowercase characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('Password policy requires lowercase characters') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[0]] + ); + + plugin.run(cache, {}, callback); + }); + + it('should give warn result if password policy does not require lowercase characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('Password policy does not require lowercase characters') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[1]] + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/identity/passwordRequiresNumbers.spec.js b/plugins/oracle/identity/passwordRequiresNumbers.spec.js new file mode 100644 index 0000000000..ea55f29451 --- /dev/null +++ b/plugins/oracle/identity/passwordRequiresNumbers.spec.js @@ -0,0 +1,138 @@ +var expect = require('chai').expect; +var plugin = require('./passwordRequiresNumbers'); + +const authenticationPolicy = [ + { + "passwordPolicy": { + "minimumPasswordLength": 15, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + }, + { + "passwordPolicy": { + "minimumPasswordLength": 12, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": false, + "isNumericCharactersRequired": false, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + authenticationPolicy: { + get: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('passwordRequiresNumbers', function () { + describe('run', function () { + it('should give unknown result if unable to query for password policy status', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for password policy status') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + {err: 'error'}, + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no password policies found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No password policies found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if password policy requires numeric characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('Password policy requires numeric characters') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[0]] + ); + + plugin.run(cache, {}, callback); + }); + + it('should give warn result if password policy does not require numeric characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('Password policy does not require numeric characters') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[1]] + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/identity/passwordRequiresSymbols.spec.js b/plugins/oracle/identity/passwordRequiresSymbols.spec.js new file mode 100644 index 0000000000..2cd9e74265 --- /dev/null +++ b/plugins/oracle/identity/passwordRequiresSymbols.spec.js @@ -0,0 +1,138 @@ +var expect = require('chai').expect; +var plugin = require('./passwordRequiresSymbols'); + +const authenticationPolicy = [ + { + "passwordPolicy": { + "minimumPasswordLength": 15, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + }, + { + "passwordPolicy": { + "minimumPasswordLength": 12, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": false, + "isNumericCharactersRequired": false, + "isSpecialCharactersRequired": false, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + authenticationPolicy: { + get: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('passwordRequiresSymbols', function () { + describe('run', function () { + it('should give unknown result if unable to query for password policy status', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for password policy status') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + {err: 'error'}, + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no password policies found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No password policies found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if password policy requires special characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('Password policy requires special characters') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[0]] + ); + + plugin.run(cache, {}, callback); + }); + + it('should give warn result if password policy does not require special characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('Password policy does not require special characters') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[1]] + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/identity/passwordRequiresUppercase.spec.js b/plugins/oracle/identity/passwordRequiresUppercase.spec.js new file mode 100644 index 0000000000..d712234c2a --- /dev/null +++ b/plugins/oracle/identity/passwordRequiresUppercase.spec.js @@ -0,0 +1,138 @@ +var expect = require('chai').expect; +var plugin = require('./passwordRequiresUppercase'); + +const authenticationPolicy = [ + { + "passwordPolicy": { + "minimumPasswordLength": 15, + "isUppercaseCharactersRequired": true, + "isLowercaseCharactersRequired": true, + "isNumericCharactersRequired": true, + "isSpecialCharactersRequired": true, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + }, + { + "passwordPolicy": { + "minimumPasswordLength": 12, + "isUppercaseCharactersRequired": false, + "isLowercaseCharactersRequired": false, + "isNumericCharactersRequired": false, + "isSpecialCharactersRequired": false, + "isUsernameContainmentAllowed": false, + "isPasswordResetEnabled": true + } + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + authenticationPolicy: { + get: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('passwordRequiresUppercase', function () { + describe('run', function () { + it('should give unknown result if unable to query for password policy status', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for password policy status') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + {err: 'error'}, + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no password policies found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No password policies found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if password policy requires uppercase characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('Password policy requires uppercase characters') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[0]] + ); + + plugin.run(cache, {}, callback); + }); + + it('should give warn result if password policy does not require uppercase characters', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('Password policy does not require uppercase characters') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [authenticationPolicy[1]] + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/identity/usersEmailVerified.js b/plugins/oracle/identity/usersEmailVerified.js new file mode 100644 index 0000000000..4f8bf9ed58 --- /dev/null +++ b/plugins/oracle/identity/usersEmailVerified.js @@ -0,0 +1,49 @@ +var helpers = require('../../../helpers/oracle'); + +module.exports = { + title: 'Users Email Verified', + category: 'Identity', + description: 'Ensure all IAM user accounts have a valid and current email address.', + more_info: 'To Have a valid email address associated with an OCI IAM local user account enables you to tie the account to identity in your organization ' + + 'as well as allows that user to reset their password if it is forgotten or lost.', + link: 'https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/managingusers.htm', + recommended_action: 'Modify IAM users to add their email addresses', + apis: ['user:list'], + + run: function (cache, settings, callback) { + var results = []; + var source = {}; + + var region = helpers.objectFirstKey(cache['regionSubscription']['list']); + + var users = helpers.addSource(cache, source, + ['user', 'list', region]); + + if (!users) return callback(null, results, source); + + if (users.err || !users.data) { + helpers.addResult(results, 3, + 'Unable to query for users: ' + helpers.addError(users)); + return callback(null, results, source); + } + + if (users.data.length < 2) { + helpers.addResult(results, 0, 'No user accounts found'); + return callback(null, results, source); + } + + users.data.forEach(user => { + if (!user.id || !user.name) return; + + if (user.email && user.email.length && user.emailVerified) { + helpers.addResult(results, 0, `Email for user ${user.name} is verified`, region, user.id); + } else if (!user.email || !user.email.length) { + helpers.addResult(results, 2, `Email for user ${user.name} not found`, region, user.id); + } else { + helpers.addResult(results, 2, `Email for user ${user.name} is not verified`, region, user.id); + } + }); + + callback(null, results, source); + } +}; diff --git a/plugins/oracle/identity/usersEmailVerified.spec.js b/plugins/oracle/identity/usersEmailVerified.spec.js new file mode 100644 index 0000000000..c16f5b0f16 --- /dev/null +++ b/plugins/oracle/identity/usersEmailVerified.spec.js @@ -0,0 +1,181 @@ +var expect = require('chai').expect; +var plugin = require('./usersEmailVerified'); + +const user = [ + { + "description": "login user", + "email": "user1@gmail.com", + "emailVerified": true, + "external-identifier": null, + "freeform-tags": {}, + "identity-provider-id": null, + "inactive-status": null, + "is-mfa-activated": false, + "last-successful-login-time": "2021-02-22T17:20:19.791000+00:00", + "lifecycle-state": "ACTIVE", + "name": "user1", + "id": "111", + "previous-successful-login-time": null, + "time-created": "2021-02-16T18:05:07.150000+00:00" + }, + { + "description": "login user", + "email": "user3@gmail.com", + "emailVerified": false, + "external-identifier": null, + "freeform-tags": {}, + "identity-provider-id": null, + "inactive-status": null, + "is-mfa-activated": false, + "id": "111", + "last-successful-login-time": "2021-02-22T17:20:19.791000+00:00", + "lifecycle-state": "ACTIVE", + "name": "user3", + "previous-successful-login-time": null, + "time-created": "2021-02-16T18:05:07.150000+00:00" + }, + { + "defined-tags": {}, + "description": "login user", + "email": "", + "external-identifier": null, + "freeform-tags": {}, + "identity-provider-id": null, + "inactive-status": null, + "is-mfa-activated": false, + "last-successful-login-time": "2021-02-25T15:53:51.093000+00:00", + "lifecycle-state": "ACTIVE", + "id": "111", + "name": "user2", + "previous-successful-login-time": null, + "time-created": "2021-02-16T17:55:53.412000+00:00" + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + user: { + list: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('usersEmailVerified', function () { + describe('run', function () { + it('should give unknown result if unable to query for users', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for users') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + {err: 'error'}, + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if No user accounts found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No user accounts found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [user[1]] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give failing result if user does not have an email', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[2].status).to.equal(2) + expect(results[2].message).to.include('not found') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + user + ); + + plugin.run(cache, {}, callback); + }) + + it('should give failing result if user email is not verified', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[1].status).to.equal(2) + expect(results[1].message).to.include('not verified') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + user + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if user email is verified', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('is verified') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + user + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/identity/usersMfaEnabled.spec.js b/plugins/oracle/identity/usersMfaEnabled.spec.js new file mode 100644 index 0000000000..5752aea8f0 --- /dev/null +++ b/plugins/oracle/identity/usersMfaEnabled.spec.js @@ -0,0 +1,130 @@ +var expect = require('chai').expect; +var plugin = require('./usersMfaEnabled'); + +const user = [ + { + "description": "login user", + "email": "user3@gmail.com", + "emailVerified": false, + "isMfaActivated": true, + "id": "111" + }, + { + "defined-tags": {}, + "description": "login user", + "email": "user1@gmail.com", + "isMfaActivated": false, + "id": "111" + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + user: { + list: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('usersMfaEnabled', function () { + describe('run', function () { + it('should give unknown result if unable to query for users', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for user') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + {err: 'error'}, + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if No user accounts found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No user accounts found') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + [user[1]] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give failing result if user has MFA disabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[1].status).to.equal(2) + expect(results[1].message).to.include('The user has MFA disabled') + expect(results[1].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + user + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if user has MFA enabled', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('The user has MFA enabled') + expect(results[0].region).to.equal('global') + done() + }; + + const cache = createCache( + null, + user + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file diff --git a/plugins/oracle/objectstore/bucketPublicAccessType.spec.js b/plugins/oracle/objectstore/bucketPublicAccessType.spec.js new file mode 100644 index 0000000000..6b9df711a6 --- /dev/null +++ b/plugins/oracle/objectstore/bucketPublicAccessType.spec.js @@ -0,0 +1,134 @@ +var expect = require('chai').expect; +var plugin = require('./bucketPublicAccessType'); + +const getBucket = [ + { + "namespace": 'idacicrnmktm', + "name": 'my-bucket', + "id": 'ocid1.bucket.oc1.iad.111111111111111122222222222222222233333333333333333', + "compartmentId": 'ocid1.tenancy.oc1..11111111111111111222222222222222222333333333333333', + "createdBy": 'ocid1.user.oc1..11111111111111112222222222222223333333333333333', + "timeCreated": '2021-04-28T13:26:51.917Z', + "publicAccessType": 'NoPublicAccess', + }, + { + "namespace": 'idacicrnmktm', + "name": 'akhtar-bucket', + "id": 'ocid1.bucket.oc1.iad.111111111111111122222222222222222233333333333333333', + "compartmentId": 'ocid1.tenancy.oc1..11111111111111111222222222222222222333333333333333', + "createdBy": 'ocid1.user.oc1..11111111111111112222222222222223333333333333333', + "timeCreated": '2021-04-28T13:26:51.917Z', + "publicAccessType": 'ObjectRead' + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + bucket: { + get: { + 'us-ashburn-1': { + err: err, + data: data + } + } + } + } +}; + +describe('bucketPublicAccessType', function () { + describe('run', function () { + it('should give unknown result if unable to query for object store bucket details', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for object store bucket details') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + ['hello'], + undefined + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no object store buckets', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No object store bucket details to check') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give failing result if bucket allows public access', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(2) + expect(results[0].message).to.include('allows') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [getBucket[1]] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if bucket does not allow public access', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('does not allow public access') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [getBucket[0]] + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file