From 4d54599a459b789c085dc7811320f0a82f1772d2 Mon Sep 17 00:00:00 2001 From: fatima99s Date: Fri, 14 Jun 2024 21:55:40 +0500 Subject: [PATCH 1/2] F/GCP-FunctionDefaultSeviceAccount --- exports.js | 1 + .../functionDefaultServiceAccount.js | 55 ++++++++ .../functionDefaultServiceAccount.spec.js | 117 ++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 plugins/google/cloudfunctions/functionDefaultServiceAccount.js create mode 100644 plugins/google/cloudfunctions/functionDefaultServiceAccount.spec.js diff --git a/exports.js b/exports.js index 9ca097916d..5289403245 100644 --- a/exports.js +++ b/exports.js @@ -1552,6 +1552,7 @@ module.exports = { 'instanceNodeCount' : require(__dirname + '/plugins/google/spanner/instanceNodeCount.js'), 'httpTriggerRequireHttps' : require(__dirname + '/plugins/google/cloudfunctions/httpTriggerRequireHttps.js'), + 'functionDefaultServiceAccount' : require(__dirname + '/plugins/google/cloudfunctions/functionDefaultServiceAccount.js'), 'ingressAllTrafficDisabled' : require(__dirname + '/plugins/google/cloudfunctions/ingressAllTrafficDisabled.js'), 'cloudFunctionLabelsAdded' : require(__dirname + '/plugins/google/cloudfunctions/cloudFunctionLabelsAdded.js'), 'functionAllUsersPolicy' : require(__dirname + '/plugins/google/cloudfunctions/functionAllUsersPolicy.js'), diff --git a/plugins/google/cloudfunctions/functionDefaultServiceAccount.js b/plugins/google/cloudfunctions/functionDefaultServiceAccount.js new file mode 100644 index 0000000000..9aa62b6a3b --- /dev/null +++ b/plugins/google/cloudfunctions/functionDefaultServiceAccount.js @@ -0,0 +1,55 @@ +var async = require('async'); +var helpers = require('../../../helpers/google'); + +module.exports = { + title: 'Cloud Function Default Service Account', + category: 'Cloud Functions', + domain: 'Serverless', + severity: 'Low', + description: 'Ensures that Cloud Functions are not using the default service account.', + more_info: 'Cloud Functions should use customized service accounts that have minimal privileges to run. Default service account has the editor role permissions. Due to security reasons it should not be used for any cloud function.', + link: 'https://cloud.google.com/functions/docs/securing', + recommended_action: 'Ensure that no Cloud Functions are using the default service account.', + apis: ['functions:list'], + realtime_triggers: ['functions.CloudFunctionsService.UpdateFunction', 'functions.CloudFunctionsService.CreateFunction', 'functions.CloudFunctionsService.DeleteFunction'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(); + + async.each(regions.functions, (region, rcb) => { + var functions = helpers.addSource(cache, source, + ['functions', 'list', region]); + + if (!functions) return rcb(); + + if (functions.err || !functions.data) { + helpers.addResult(results, 3, + 'Unable to query for Google Cloud Functions: ' + helpers.addError(functions), region, null, null, functions.err); + return rcb(); + } + + if (!functions.data.length) { + helpers.addResult(results, 0, 'No Google Cloud functions found', region); + return rcb(); + } + + functions.data.forEach(func => { + if (!func.name) return; + + if (func.serviceAccountEmail && func.serviceAccountEmail.endsWith('@appspot.gserviceaccount.com')) { + helpers.addResult(results, 2, + 'Cloud Function is using default service account', region, func.name); + } else { + helpers.addResult(results, 0, + 'Cloud Function is not using default service account', region, func.name); + } + }); + + rcb(); + }, function() { + callback(null, results, source); + }); + } +}; \ No newline at end of file diff --git a/plugins/google/cloudfunctions/functionDefaultServiceAccount.spec.js b/plugins/google/cloudfunctions/functionDefaultServiceAccount.spec.js new file mode 100644 index 0000000000..2688b0de3e --- /dev/null +++ b/plugins/google/cloudfunctions/functionDefaultServiceAccount.spec.js @@ -0,0 +1,117 @@ +var expect = require('chai').expect; +var plugin = require('./functionDefaultServiceAccount'); + + +const functions = [ + { + "name": "projects/my-test-project/locations/us-central1/functions/function-1", + "status": "ACTIVE", + "entryPoint": "helloWorld", + "timeout": "60s", + "availableMemoryMb": 256, + "updateTime": "2021-09-24T06:18:15.265Z", + "runtime": "nodejs14", + "ingressSettings": "ALLOW_ALL", + "serviceAccountEmail": "aqua@appspot.gserviceaccount.com" + }, + { + "name": "projects/my-test-project/locations/us-central1/functions/function-1", + "status": "ACTIVE", + "entryPoint": "helloWorld", + "timeout": "60s", + "availableMemoryMb": 256, + "updateTime": "2021-09-24T06:18:15.265Z", + "versionId": "1", + "runtime": "nodejs14", + "ingressSettings": "ALLOW_INTERNAL_AND_GCLB", + "labels": { 'deployment-tool': 'console-cloud' }, + "serviceAccountEmail": "aqua-982@akhtar-dev-aqua.iam.gserviceaccount.com" + + } +]; + +const createCache = (list, err) => { + return { + functions: { + list: { + 'us-central1': { + err: err, + data: list + } + } + } + } +}; + +describe('functionDefaultServiceAccount', function () { + describe('run', function () { + it('should give passing result if no cloud functions 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 Google Cloud functions found'); + expect(results[0].region).to.equal('us-central1'); + done() + }; + + const cache = createCache( + [], + null + ); + + plugin.run(cache, {}, callback); + }); + + it('should give unknown result if unable to query for Google Cloud functions', 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 Google Cloud Functions'); + expect(results[0].region).to.equal('us-central1'); + done() + }; + + const cache = createCache( + [], + {message: 'error'}, + ); + + plugin.run(cache, {}, callback); + }); + + it('should give passing result if google cloud function is not using default service account', 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('Cloud Function is not using default service account'); + expect(results[0].region).to.equal('us-central1'); + done() + }; + + const cache = createCache( + [functions[1]], + null + ); + + plugin.run(cache, {}, callback); + }); + + it('should give failing result if google cloud function is using service account', 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('Cloud Function is using default service account'); + expect(results[0].region).to.equal('us-central1'); + done(); + }; + + const cache = createCache( + [functions[0]], + null ); + + plugin.run(cache, {}, callback); + }); + + }) +}); + From 2eebfed0a528935ec1add1cf59adb318862d0b42 Mon Sep 17 00:00:00 2001 From: alphadev4 <113519745+alphadev4@users.noreply.github.com> Date: Thu, 20 Jun 2024 00:46:00 +0500 Subject: [PATCH 2/2] Apply suggestions from code review --- .../google/cloudfunctions/functionDefaultServiceAccount.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/google/cloudfunctions/functionDefaultServiceAccount.js b/plugins/google/cloudfunctions/functionDefaultServiceAccount.js index 9aa62b6a3b..2952ab99f4 100644 --- a/plugins/google/cloudfunctions/functionDefaultServiceAccount.js +++ b/plugins/google/cloudfunctions/functionDefaultServiceAccount.js @@ -5,10 +5,10 @@ module.exports = { title: 'Cloud Function Default Service Account', category: 'Cloud Functions', domain: 'Serverless', - severity: 'Low', + severity: 'Medium', description: 'Ensures that Cloud Functions are not using the default service account.', - more_info: 'Cloud Functions should use customized service accounts that have minimal privileges to run. Default service account has the editor role permissions. Due to security reasons it should not be used for any cloud function.', - link: 'https://cloud.google.com/functions/docs/securing', + more_info: 'Using the default service account for Cloud Functions can lead to privilege escalation and overly permissive access. It is recommended to use a user-managed service account for each function in a project instead of the default service account. A managed service account allows more precise access control by granting only the necessary permissions through Identity and Access Management (IAM).', + link: 'https://cloud.google.com/functions/docs/securing/function-identity', recommended_action: 'Ensure that no Cloud Functions are using the default service account.', apis: ['functions:list'], realtime_triggers: ['functions.CloudFunctionsService.UpdateFunction', 'functions.CloudFunctionsService.CreateFunction', 'functions.CloudFunctionsService.DeleteFunction'],