Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PXP-7807 Audit-service tests #644

Merged
merged 20 commits into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
6,841 changes: 122 additions & 6,719 deletions .secrets.baseline

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions codecept.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ exports.config = {
manifestService: './services/apis/manifestService/manifestServiceService.js',
guppy: './services/apis/guppy/guppyService.js',
mds: './services/apis/mds/mdsService.js',
auditService: './services/apis/auditService/auditService.js',

// Pages
home: './services/portal/home/homeService.js',
Expand Down
6 changes: 1 addition & 5 deletions docs/testplans/audit-sevice/audit-service-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,4 @@ Step #1 might be tricky since we can't easily automate going through the OIDC fl
1. Index file X.
2. User C does not have access to download file X. User C requests a presigned URL from Fence to download file X. The request is unsuccessful.
3. User B has access to query presigned URL audit logs. User B queries logs by making a call to the `GET /audit/log/presigned_url` endpoint. Use a filter: `guid=<file X GUID>`.
4. Make sure there is no log describing step #2.

## TODOs

- We may need to update these scenarios and add new ones in the future as more audit log categories are implemented, or if we start recording unsuccessful events.
4. The log describing step #2 is returned. Make sure the values look right.
23 changes: 23 additions & 0 deletions run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,29 @@ elif ! (g3kubectl get pods --no-headers -l app=hatchery | grep hatchery) > /dev/
donot '@exportToWorkspacePortalHatchery'
fi

#
# only run audit-service tests for manifest repos IF audit-service is
# deployed, and for repos with an audit-service integration.
#
runAuditTests=true
if ! [[ "$service" =~ ^(audit-service|fence|cloud-automation|gen3-qa)$ ]]; then
if [[ "$service" =~ ^(cdis-manifest|gitops-qa|gitops-dev)$ ]]; then
if ! (g3kubectl get pods --no-headers -l app=audit-service | grep audit-service) > /dev/null 2>&1; then
echo "INFO: audit-service is not deployed"
runAuditTests=false
fi
else
echo "INFO: no need to run audit-service tests for repo $service"
runAuditTests=false
fi
fi
if [[ "$runAuditTests" == true ]]; then
echo "INFO: enabling audit-service tests"
else
echo "INFO: disabling audit-service tests"
donot '@audit'
fi

########################################################################################

testArgs="--reporter mocha-multi"
Expand Down
7 changes: 7 additions & 0 deletions services/apis/auditService/auditService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const auditServiceProps = require('./auditServiceProps.js');
const auditServiceTasks = require('./auditServiceTasks.js');

module.exports = {
props: auditServiceProps,
do: auditServiceTasks,
};
5 changes: 5 additions & 0 deletions services/apis/auditService/auditServiceProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
endpoints: {
query: '/audit/log',
},
};
124 changes: 124 additions & 0 deletions services/apis/auditService/auditServiceTasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const chai = require('chai');

const auditServiceProps = require('./auditServiceProps.js');
const { smartWait } = require('../../../utils/apiUtil');

const { expect } = chai;
const I = actor();

async function waitForAuditLogs(category, userTokenHeader, params, nLogs) {
/**
* Wait until the audit-service has processed `nLogs` as expected.
* @param {string} category - audit log category
* @param {string} userTokenHeader - headers to use for authoriation
* @param {string[]} params - optional query parameters
* @param {boolean} nLogs - number of logs we expect to receive
*/
const areLogsThere = async function (_category, _userTokenHeader, _params, _nLogs) {
/**
* Return true if the expected number of audit logs have been processed,
* false otherwise
* @param {string} _category - audit log category
* @param {string} _userTokenHeader - headers to use for authoriation
* @param {string[]} _params - optional query parameters
* @param {boolean} _nLogs - number of logs we expect to receive
*/
// query audit logs
const json = await module.exports.query(_category, _userTokenHeader, _params);
if (json.data.length === _nLogs) {
return true;
} if (json.data.length > _nLogs) {
// this should never happen
console.error(`Expected to receive ${_nLogs} audit logs or less, bugt received ${json.data.length}.`);
}
console.log(`Expecting ${_nLogs} logs, received ${json.data.length} logs - waiting...`);
return false;
};

// wait up to 5 min - it can take very long for the SQS to return
// messages to the audit-service...
const timeout = 300;
const startWait = 1; // initial number of seconds to wait
const errorMessage = `The audit-service did not process ${nLogs} logs as expected after ${timeout} seconds`;

await smartWait(
areLogsThere,
[category, userTokenHeader, params, nLogs],
timeout,
errorMessage,
startWait,
);
}

module.exports = {
async query(logCategory, userTokenHeader, params = [], expectedStatus = 200) {
/**
* Hit the audit-service query endpoint.
* @param {string} logCategory - audit log category
* @param {string} userTokenHeader - headers to use for authoriation
* @param {string[]} params - optional query parameters
* @param {boolean} expectedStatus - expected status code, if not 200
* @returns {Promise<list>} - data returned by the audit-service
*/
let url = `${auditServiceProps.endpoints.query}/${logCategory}`;
if (params && params.length > 0) {
url += `?${params.join('&')}`;
}
const response = await I.sendGetRequest(url, userTokenHeader);
expect(response, 'Audit logs query failed').to.have.property('status', expectedStatus);

if (expectedStatus === 200) {
expect(response, 'No data in response').to.have.property('data');
const json = response.data;
expect(json, 'No data in JSON response').to.have.property('data');
expect(json, 'No nextTimeStamp in JSON response').to.have.property('nextTimeStamp');
return json;
}

return {};
},

async checkQueryResults(
logCategory,
userTokenHeader,
params = [],
nExpectedLogs,
expectedResults,
) {
/**
* Check if querying the audit-service returns the logs we expect.
* @param {string} logCategory - audit log category
* @param {string} userTokenHeader - headers to use for authoriation
* @param {string[]} params - optional query parameters
* @param {int} nExpectedLogs - expected number of logs
* @param {Object[]} expectedResults - values we expect to see in the logs
*/
// we need some buffer time for the audit logs to be processed
await waitForAuditLogs(logCategory, userTokenHeader, params, nExpectedLogs);

// query audit logs starting at time 'timestamp'
const json = await module.exports.query(logCategory, userTokenHeader, params);
const receivedLogs = json.data;
console.log('Received logs:');
console.log(receivedLogs);
expect(receivedLogs.length, `Should receive exactly ${nExpectedLogs} audit logs but received ${receivedLogs}`).to.equal(nExpectedLogs);

// check that the returned audit logs contain the data we expect.
// the latest audit-service returns audit logs in the order they were
// created, but some older versions might not, so we can't assume they're
// in the right order.
expectedResults.forEach((expectedResult) => {
console.log('Checking expected result:');
console.log(expectedResult);
// check that we received a log that matches the current expected log.
// found==true if we found a received log for which all the fields match
// the current expected log, false otherwise.
const found = receivedLogs.some(
(receivedLog) => Object.entries(expectedResult).every(
([field, expectedValue]) => receivedLog[field] === expectedValue,
),
);
expect(found, 'The audit log I expect is not in the logs I received').to.be.true;
});
},
};
9 changes: 3 additions & 6 deletions services/apis/indexd/indexdTasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,10 @@ module.exports = {
return data;
}).map((data) => I.sendPostRequest(indexdProps.endpoints.add, data, authHeaders).then(
(res) => {
if (res.status === 200 && res.data && res.data.rev) {
console.log('### ## Do we ever enter this block?');
file.rev = res.data.rev; // eslint-disable-line no-undef
return Promise.resolve(file); // eslint-disable-line no-undef
if (res.status !== 200 || !res.data || !res.data.rev) {
console.error(`Failed indexd submission got status ${res.status}`, res.data);
return Promise.reject(new Error('Failed to register file with indexd'));
}
console.error(`Failed indexd submission got status ${res.status}`, res.data);
return Promise.reject(new Error('Failed to register file with indexd'));
},
(err) => {
console.err(`Error on indexd submission ${data.file_name}`, err);
Expand Down
Loading