Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions functions/v2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]

Expand All @@ -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/<PROJECT>/zones/<ZONE>/instances/<INSTANCE>
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, '_');
Expand Down
4 changes: 2 additions & 2 deletions functions/v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
54 changes: 49 additions & 5 deletions functions/v2/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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/);
Expand Down Expand Up @@ -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: '[email protected]',
requestMetadata: {
callerIp: '8.8.8.8',
callerSuppliedUserAgent: 'example-user-agent',
},
request: {
'@type': 'type.googleapis.com/storage.objects.write',
},
resourceName: 'some-resource',
},
},
};
Expand All @@ -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/);
});
});