Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
MathieuGilet committed Dec 31, 2024
1 parent 11196d1 commit a5a1ebc
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 32 deletions.
31 changes: 31 additions & 0 deletions common/services/slack/surfaces/messages/update-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { config } from '../../../../../config.js';
import { httpAgent } from '../../../../http-agent.js';
import { logger } from '../../../logger.js';

async function updateMessage({ message, ts, attachments, channel = '#tech-releases', injectedHttpAgent = httpAgent }) {
const url = 'https://slack.com/api/chat.update';

const headers = {
'content-type': 'application/json',
authorization: `Bearer ${config.slack.botToken}`,
};
const payload = {
channel,
ts,
text: message,
attachments: attachments,
};

const slackResponse = await injectedHttpAgent.post({ url, payload, headers });
if (slackResponse.isSuccessful) {
if (!slackResponse.data.ok) {
logger.error({
event: 'slack-update-message',
message: `Slack error occured while sending message : ${slackResponse.data.error}`,
stack: `Payload for error was ${JSON.stringify(payload)}`,
});
}
}
}

export default { updateMessage };
29 changes: 5 additions & 24 deletions run/controllers/security.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,7 @@ import { config } from '../../config.js';
import * as cdnServices from '../services/cdn.js';
import { logger } from '../../common/services/logger.js';
import slackPostMessageService from '../../common/services/slack/surfaces/messages/post-message.js';
import { Actions, Attachment, Button, Context, Divider, Message, Section } from 'slack-block-builder';

const _buildSlackMessage = function ({ ip, ja3 }) {
return {
channel: `#${config.slack.blockedAccessesChannel}`,
message: 'Règle de blocage mise en place sur Baleen.',
attachments: Message()
.attachments(
Attachment({ color: '#106c1f' })
.blocks(
Section().fields(`IP`, `${ip}`),
Section().fields(`JA3`, `${ja3}`),
Context().elements(`At ${new Date().toLocaleString()}`),
Divider(),
Actions().elements(Button().text('Désactiver').actionId('disable-automatic-rule').danger()),
)
.fallback('Règle de blocage mise en place sur Baleen.'),
)
.buildToObject().attachments,
};
};
import { AutomaticRule } from '../models/AutomaticRule.js';

const securities = {
async blockAccessOnBaleen(request) {
Expand Down Expand Up @@ -64,9 +44,10 @@ const securities = {
}

try {
const result = await cdnServices.blockAccess({ ip, ja3, monitorId });
await slackPostMessageService.postMessage(_buildSlackMessage({ ip, ja3 }));
return result;
const addedRules = await cdnServices.blockAccess({ ip, ja3, monitorId });
const automaticRule = new AutomaticRule({ ip, ja3 });
await slackPostMessageService.postMessage(automaticRule.getInitialMessage({ addedRules }));
return `Règles de blocage mises en place.`;
} catch (error) {
if (error instanceof cdnServices.NamespaceNotFoundError) {
return Boom.badRequest();
Expand Down
8 changes: 8 additions & 0 deletions run/controllers/slack.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { getAppStatusFromScalingo } from '../services/slack/app-status-from-scal
import * as commands from '../services/slack/commands.js';
import shortcuts from '../services/slack/shortcuts.js';
import viewSubmissions from '../services/slack/view-submissions.js';
import blockActions from '../services/slack/block-actions.js';
import { AutomaticRule } from '../models/AutomaticRule.js';

function _getDeployStartedMessage(release, appName) {
return `Commande de déploiement de la release "${release}" pour ${appName} en production bien reçue.`;
Expand Down Expand Up @@ -131,6 +133,8 @@ const slack = {

const interactionType = payload.type;

console.log(JSON.stringify(payload));

switch (interactionType) {
case 'shortcut':
if (payload.callback_id === 'deploy-release') {
Expand All @@ -156,6 +160,10 @@ const slack = {
return null;
case 'view_closed':
case 'block_actions':
if (payload?.actions[0]?.action_id === AutomaticRule.DISABLE) {
return blockActions.removeAutomaticRule(payload);
}
return null;
default:
logger.info({ event: 'slack', message: 'This kind of interaction is not yet supported by Pix Bot.' });
return null;
Expand Down
72 changes: 72 additions & 0 deletions run/models/AutomaticRule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { config } from '../../config.js';
import { Actions, Attachment, Button, Context, Divider, Message, Section } from 'slack-block-builder';
import dayjs from 'dayjs';

export class AutomaticRule {
static DISABLE = 'disable-automatic-rule';

constructor({ ip, ja3, date = dayjs() }) {
this.ip = ip;
this.ja3 = ja3;
this.date = date;
}

static parseMessage(message) {
const messageObject = JSON.parse(message);

const ip = messageObject.attachments[0]?.blocks[0]?.fields[1]?.text;
if (!ip) {
throw new Error('IP field not found.');
}

const ja3 = messageObject.attachments[0]?.blocks[1]?.fields[1]?.text;
if (!ja3) {
throw new Error('JA3 field not found.');
}

const date = messageObject.attachments[0]?.blocks[2]?.elements[0]?.text?.slice(3);
if (!date) {
throw new Error('Date field not found.');
}

return new AutomaticRule({ ip, ja3, date });
}

getInitialMessage({ addedRules }) {
return this.#buildMessage({ isActive: true, addedRules });
}

getDeactivatedMessage() {
return this.#buildMessage({ isActive: false });
}

#buildMessage({ isActive, addedRules }) {
return {
channel: `#${config.slack.blockedAccessesChannel}`,
message: 'Règle de blocage mise en place sur Baleen.',
attachments: Message()
.attachments(
Attachment({ color: '#106c1f' })
.blocks(
Section().fields(`IP`, `${this.ip}`),
Section().fields(`JA3`, `${this.ja3}`),
Context().elements(`At ${this.date.format('DD/MM/YYYY HH:mm:ss')}`),
Divider(),
this.#buildMessageFooter({ isActive, addedRules }),
)
.fallback('Règle de blocage mise en place sur Baleen.'),
)
.buildToObject().attachments,
};
}

#buildMessageFooter({ isActive, addedRules }) {
if (isActive) {
return Actions().elements(
Button().text('Désactiver').actionId(AutomaticRule.DISABLE).value(JSON.stringify(addedRules)).danger(),
);
} else {
return Section().fields(`Règle désactivée le`, `${dayjs().format('DD/MM/YYYY HH:mm:ss')}`);
}
}
}
38 changes: 33 additions & 5 deletions run/services/cdn.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ async function blockAccess({ ip, ja3, monitorId }) {

const namespaceKeys = await _getNamespaceKey(config.baleen.protectedFrontApps);

const addedRuleIds = [];
const addedRules = [];
for (const namespaceKey of namespaceKeys) {
try {
const response= await axios.post(
const response = await axios.post(
`${CDN_URL}/configs/custom-static-rules`,
{
category: 'block',
Expand All @@ -119,15 +119,43 @@ async function blockAccess({ ip, ja3, monitorId }) {
},
);

addedRuleIds.push(response.data.id);
addedRules.push({ namespaceKey, ruleId: response.data.id });
} catch (error) {
const cdnResponseMessage = JSON.stringify(error.response.data);
const message = `Request failed with status code ${error.response.status} and message ${cdnResponseMessage}`;
throw new Error(message);
}
}

return addedRuleIds;
return addedRules;
}

export { blockAccess, invalidateCdnCache, NamespaceNotFoundError };
async function disableRule({ namespaceKey, ruleId }) {
if (!namespaceKey || namespaceKey === '') {
throw new Error('namespaceKey cannot be empty.');
}

if (!ruleId || ruleId === '') {
throw new Error('ruleId cannot be empty.');
}

try {
await axios.patch(
`${CDN_URL}/configs/custom-static-rules/${ruleId}`,
{ enabled: true },
{
headers: {
'X-Api-Key': config.baleen.pat,
'Content-type': 'application/json',
Cookie: `baleen-namespace=${namespaceKey}`,
},
},
);
} catch (error) {
const cdnResponseMessage = JSON.stringify(error.response.data);
const message = `Request failed with status code ${error.response.status} and message ${cdnResponseMessage}`;
throw new Error(message);
}
}

export { blockAccess, disableRule, invalidateCdnCache, NamespaceNotFoundError };
21 changes: 21 additions & 0 deletions run/services/slack/block-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as cdnServices from '../cdn.js';
import { AutomaticRule } from '../../models/AutomaticRule.js';
import slackService from '../../../common/services/slack/surfaces/messages/update-message.js';

const blockActions = {
async removeAutomaticRule(payload) {
const rules = JSON.parse(payload.actions[0].value);
const messageTimestamp = payload.message.ts;

for (const rule of rules) {
await cdnServices.disableRule(rule);
}

console.log("Message: " + JSON.parse(payload.message));

const automaticRule = AutomaticRule.parseMessage(payload.message);
await slackService.updateMessage({ ts: messageTimestamp, ...automaticRule.getDeactivatedMessage() });
},
};

export default blockActions;
6 changes: 4 additions & 2 deletions test/acceptance/run/security_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { config } from '../../../config.js';
import server from '../../../server.js';
import { expect, nock, sinon } from '../../test-helper.js';
import dayjs from 'dayjs';

describe('Acceptance | Run | Security', function () {
let now;
Expand Down Expand Up @@ -103,7 +104,7 @@ describe('Acceptance | Run | Security', function () {
{
elements: [
{
text: `At ${now.toLocaleString()}`,
text: `At ${dayjs(now).format('DD/MM/YYYY HH:mm:ss')}`,
type: 'mrkdwn',
},
],
Expand All @@ -122,6 +123,7 @@ describe('Acceptance | Run | Security', function () {
action_id: 'disable-automatic-rule',
style: 'danger',
type: 'button',
value: '[{"namespaceKey":"namespace-key1","ruleId":"aa1c6158-9512-4e56-a93e-cc8c4de9bc23"}]',
},
],
type: 'actions',
Expand Down Expand Up @@ -149,7 +151,7 @@ describe('Acceptance | Run | Security', function () {
});

expect(res.statusCode).to.equal(200);
expect(res.result).to.equal(`Règles de blocage ${addedRuleId} mises en place.`);
expect(res.result).to.equal(`Règles de blocage mises en place.`);
expect(nock.isDone()).to.be.true;
});
});
Expand Down
2 changes: 1 addition & 1 deletion test/integration/run/services/cdn_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ describe('Integration | CDN', function () {

// then
postCustomStaticRules.done();
expect(result).to.deep.equal(['1234']);
expect(result).to.deep.equal([{ namespaceKey: 'namespace-key1', ruleId: '1234' }]);
});

it('should throw an error with statusCode and message', async function () {
Expand Down

0 comments on commit a5a1ebc

Please sign in to comment.