diff --git a/functions/v2/index.js b/functions/v2/index.js index b3a60d4322..ddebfc371a 100644 --- a/functions/v2/index.js +++ b/functions/v2/index.js @@ -56,7 +56,8 @@ exports.helloGCS = cloudevent => { // [START functions_log_cloudevent] /** - * CloudEvent function to be triggered by Cloud Audit Logging + * CloudEvent function to be triggered by an Eventarc Cloud Audit Logging trigger + * Note: this is NOT designed for second-party (Cloud Audit Logs -> Pub/Sub) triggers! * * @param {object} cloudevent A CloudEvent containing the Cloud Audit Log entry. * @param {object} cloudevent.data.protoPayload The Cloud Audit Log entry itself. @@ -68,8 +69,21 @@ exports.helloAuditLog = cloudevent => { console.log('Subject:', cloudevent.subject); // Print out details from the Cloud Audit Logging entry - const payload = cloudevent.data.protoPayload; - console.log('Principal:', payload.authenticationInfo.principalEmail); + const payload = cloudevent.data && cloudevent.data.protoPayload; + if (payload) { + console.log('Resource name:', payload.resourceName); + } + + const request = payload.request; + if (request) { + console.log('Request type:', request['@type']); + } + + const metadata = payload && payload.requestMetadata; + if (metadata) { + console.log('Caller IP:', metadata.callerIp); + console.log('User agent:', metadata.callerSuppliedUserAgent); + } }; // [END functions_log_cloudevent] @@ -86,12 +100,19 @@ const instancesClient = new compute.InstancesClient(); */ exports.autoLabelInstance = async cloudevent => { // Extract parameters from the CloudEvent + Cloud Audit Log data - let creator = cloudevent.data.protoPayload.authenticationInfo.principalEmail; + const payload = cloudevent.data && cloudevent.data.protoPayload; + const authInfo = payload && payload.authenticationInfo; + let creator = authInfo && authInfo.principalEmail; // Get relevant VM instance details from the cloudevent's `subject` property // Example value: // compute.googleapis.com/projects//zones//instances/ - const params = cloudevent.subject.split('/'); + const params = cloudevent.subject && cloudevent.subject.split('/'); + + // Validate data + if (!creator || !params || params.length !== 7) { + throw new Error('Invalid event structure'); + } // Format the 'creator' parameter to match GCE label validation requirements creator = creator.toLowerCase().replace(/\W/g, '_'); diff --git a/functions/v2/package.json b/functions/v2/package.json index 465a548812..1ef23ce33a 100644 --- a/functions/v2/package.json +++ b/functions/v2/package.json @@ -12,8 +12,8 @@ "node": ">=12.0.0" }, "scripts": { - "test": "mocha test/index.test.js", - "e2e-test": "mocha test/*.test.js" + "test": "mocha --timeout 30s test/index.test.js", + "e2e-test": "mocha --timeout 60s test/*.test.js" }, "dependencies": { "@google-cloud/compute": "^3.0.0-alpha.4" diff --git a/functions/v2/test/index.test.js b/functions/v2/test/index.test.js index ee09416074..312ec4100b 100644 --- a/functions/v2/test/index.test.js +++ b/functions/v2/test/index.test.js @@ -31,6 +31,7 @@ const getFFOutput = ffProc => { let stderr = ''; ffProc.stdout.on('data', data => (stdout += data)); ffProc.stderr.on('data', data => (stderr += data)); + ffProc.on('error', reject).on('exit', code => { code === 0 ? resolve(stdout) : reject(stderr); }); @@ -113,6 +114,7 @@ describe('functions_cloudevent_storage', () => { ffProc.kill(); const output = await ffProcHandler; + console.log(); assert.strictEqual(response.status, 204); assert.match(output, /Event ID: 1234/); @@ -143,15 +145,20 @@ describe('functions_log_cloudevent', () => { it('should process a CloudEvent', async () => { const event = { - methodname: 'storage.objects.create', + methodname: 'storage.objects.write', type: 'google.cloud.audit.log.v1.written', subject: 'storage.googleapis.com/projects/_/buckets/my-bucket/objects/test.txt', data: { protoPayload: { - authenticationInfo: { - principalEmail: 'nobody@example.com', + requestMetadata: { + callerIp: '8.8.8.8', + callerSuppliedUserAgent: 'example-user-agent', + }, + request: { + '@type': 'type.googleapis.com/storage.objects.write', }, + resourceName: 'some-resource', }, }, }; @@ -161,12 +168,49 @@ describe('functions_log_cloudevent', () => { const output = await ffProcHandler; assert.strictEqual(response.status, 204); - assert.match(output, /API method: storage\.objects\.create/); + assert.match(output, /API method: storage\.objects\.write/); assert.match(output, /Event type: google.cloud.audit.log.v1.written/); assert.match( output, /Subject: storage.googleapis.com\/projects\/_\/buckets\/my-bucket\/objects\/test\.txt/ ); - assert.match(output, /Principal: nobody@example\.com/); + assert.match(output, /Resource name: some-resource/); + assert.match( + output, + /Request type: type\.googleapis\.com\/storage\.objects.write/ + ); + assert.match(output, /Caller IP: 8\.8\.8\.8/); + assert.match(output, /User agent: example-user-agent/); + }); +}); + +describe('functions_label_gce_instance', () => { + const PORT = 9084; + let ffProc; + let ffProcHandler; + + before(async () => { + ffProc = await startFF('autoLabelInstance', 'cloudevent', PORT); + ffProcHandler = getFFOutput(ffProc); + }); + + after(() => { + // Stop any residual Functions Framework instances + try { + ffProc.kill(); + } finally { + /* Do nothing */ + } + }); + + it('should validate data', async () => { + const event = { + subject: 'invalid/subject/example', + }; + const response = await invocation(PORT, event); + ffProc.kill(); + + await ffProcHandler; + assert.match(response.data, /Invalid event structure/); }); });