Skip to content

Commit

Permalink
feat: add POST /security/block-access-on-baleen-from-datadog
Browse files Browse the repository at this point in the history
  • Loading branch information
MathieuGilet committed Dec 11, 2024
1 parent ed79627 commit c16a2f6
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 0 deletions.
73 changes: 73 additions & 0 deletions run/controllers/security.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Boom from '@hapi/boom';

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 { Attachment, Context, Message, Section } from 'slack-block-builder';

const securities = {
async blockAccessOnBaleen(request) {
if (request.headers.authorization !== config.datadog.token) {
return Boom.unauthorized('Token is missing or is incorrect');
}

const { monitorId, body } = request.payload;

if (!body) {
const message = `Inconsistent payload : ${JSON.stringify(request.payload)}.`;
logger.warn({
event: 'block-access-on-baleen',
message,
});
return Boom.badRequest(message);
}

if (!body.match(/>>(.*)<</)) {
const message = `Inconsistent payload : ${body}.`;
logger.warn({ event: 'block-access-on-baleen', message });
return Boom.badRequest(message);
}

const { ip, ja3 } = JSON.parse(body.match(/>>(.*)<</)[1]);

if (!ip || ip === '') {
const message = 'IP is mandatory.';
logger.warn({ event: 'block-access-on-baleen', message });
return Boom.badRequest(message);
}

if (!ja3 || ja3 === '') {
const message = 'JA3 is mandatory.';
logger.warn({ event: 'block-access-on-baleen', message: message });
return Boom.badRequest(message);
}

try {
const result = await cdnServices.blockAccess({ ip, ja3, monitorId });
await slackPostMessageService.postMessage({
channel: '#baleen-automatic-rules',
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()}`),
)
.fallback('Règle de blocage mise en place sur Baleen.'),
)
.buildToObject().attachments,
});
return result;
} catch (error) {
if (error instanceof cdnServices.NamespaceNotFoundError) {
return Boom.badRequest();
}
return error;
}
},
};

export default securities;
11 changes: 11 additions & 0 deletions run/routes/security.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import securityController from '../controllers/security.js';

const securities = [
{
method: 'POST',
path: '/security/block-access-on-baleen-from-datadog',
handler: securityController.blockAccessOnBaleen,
},
];

export default securities;
2 changes: 2 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import runRoutesApplication from './run/routes/applications.js';
import deploySitesRoutes from './run/routes/deploy-sites.js';
import runRoutesManifest from './run/routes/manifest.js';
import runGitHubRoutes from './run/routes/github.js';
import runSecurityRoutes from './run/routes/security.js';

const manifests = [runManifest, buildManifest];
const setupErrorHandling = function (server) {
Expand All @@ -40,6 +41,7 @@ server.route(runRoutesApplication);
server.route(scalingoRoutes);
server.route(deploySitesRoutes);
server.route(runGitHubRoutes);
server.route(runSecurityRoutes);

registerSlashCommands(runDeployConfiguration.deployConfiguration, runManifest);

Expand Down
120 changes: 120 additions & 0 deletions test/acceptance/run/security_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { config } from '../../../config.js';
import server from '../../../server.js';
import { expect, nock } from '../../test-helper.js';

describe('Acceptance | Run | Security', function () {
describe('POST /security/block-access-on-baleen-from-datadog', function () {
it('responds with 200', async function () {
// given
const namespace = 'Pix_Namespace';
const namespaceKey = 'namespace-key1';
const monitorId = '1234';
const ip = '127.0.0.1';
const ja3 = '9709730930';

nock('https://console.baleen.cloud/api', {
reqheaders: {
'X-Api-Key': config.baleen.pat,
'Content-type': 'application/json',
},
})
.get('/account')
.reply(200, {
namespaces: {
'namespace-key2': 'Test2',
'namespace-key1': namespace,
},
});

nock('https://console.baleen.cloud/api', {
reqheaders: {
'X-Api-Key': config.baleen.pat,
'Content-type': 'application/json',
Cookie: `baleen-namespace=${namespaceKey}`,
},
})
.post('/configs/custom-static-rules', {
category: 'block',
name: `Blocage ip: ${ip} ja3: ${ja3}`,
description: `Blocage automatique depuis le monitor Datadog ${monitorId}`,
enabled: true,
labels: ['automatic-rule'],
conditions: [
[
{ type: 'ip', operator: 'match', value: ip },
{ type: 'ja3', operator: 'equals', value: ja3 },
],
],
})
.reply(200);

nock('https://slack.com')
.post('/api/chat.postMessage', {
attachments: [
{
blocks: [
{
fields: [
{
text: 'IP',
type: 'mrkdwn',
},
{
text: '127.0.0.1',
type: 'mrkdwn',
},
],
type: 'section',
},
{
fields: [
{
text: 'JA3',
type: 'mrkdwn',
},
{
text: '9709730930',
type: 'mrkdwn',
},
],
type: 'section',
},
{
elements: [
{
text: 'At 12/6/2024, 4:48:50 PM',
type: 'mrkdwn',
},
],
type: 'context',
},
],
color: '#106c1f',
fallback: 'Règle de blocage mise en place sur Baleen.',
},
],
channel: '#baleen-automatic-rules',
text: 'Règle de blocage mise en place sur Baleen.',
})
.reply(200, {
ok: true,
});

// when
const res = await server.inject({
method: 'POST',
url: '/security/block-access-on-baleen-from-datadog',
headers: {
Authorization: 'token',
},
payload: {
monitorId,
body: `>>{"ip":"${ip}","ja3":"${ja3}"}<<`,
},
});

expect(res.statusCode).to.equal(200);
expect(res.result).to.eql('Règle de blocage mise en place.');
});
});
});
155 changes: 155 additions & 0 deletions test/unit/run/controllers/security_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { expect } from '../../../test-helper.js';
import securityController from '../../../../run/controllers/security.js';

describe('Unit | Run | Controller | Security', function () {
describe('#blockAccessOnBaleen', function () {
describe('when authorization header does not match', function () {
it('should return a 401 Unauthorized', async function () {
// given
const request = {
headers: {
authorization: 'wrong-token',
},
};

// when
const response = await securityController.blockAccessOnBaleen(request);

// then
expect(response.output.statusCode).to.equal(401);
expect(response.message).to.equal('Token is missing or is incorrect');
});
});

describe('when there is no body attribute', function () {
it('should return a 400 Bad Request', async function () {
// given
const request = {
headers: {
authorization: 'token',
},
payload: {
monitorId: '1234',
},
};

// when
const response = await securityController.blockAccessOnBaleen(request);

// then
expect(response.output.statusCode).to.equal(400);
expect(response.message).to.equal('Inconsistent payload : {"monitorId":"1234"}.');
});
});

describe('when the body is malformed', function () {
it('should return a 400 Bad Request', async function () {
// given
const request = {
headers: {
authorization: 'token',
},
payload: {
monitorId: '1234',
body: 'malformed body',
},
};

// when
const response = await securityController.blockAccessOnBaleen(request);

// then
expect(response.output.statusCode).to.equal(400);
expect(response.message).to.equal('Inconsistent payload : malformed body.');
});
});

describe('when the ip parameter is absent', function () {
it('should return a 400 Bad Request', async function () {
// given
const request = {
headers: {
authorization: 'token',
},
payload: {
monitorId: '1234',
body: '>>{"ja3":"12345"}<<',
},
};

// when
const response = await securityController.blockAccessOnBaleen(request);

// then
expect(response.output.statusCode).to.equal(400);
expect(response.message).to.equal('IP is mandatory.');
});
});

describe('when the ip parameter is empty', function () {
it('should return a 400 Bad Request', async function () {
// given
const request = {
headers: {
authorization: 'token',
},
payload: {
monitorId: '1234',
body: '>>{"ip": "","ja3":"12345"}<<',
},
};

// when
const response = await securityController.blockAccessOnBaleen(request);

// then
expect(response.output.statusCode).to.equal(400);
expect(response.message).to.equal('IP is mandatory.');
});
});

describe('when the ja3 parameter is absent', function () {
it('should return a 400 Bad Request', async function () {
// given
const request = {
headers: {
authorization: 'token',
},
payload: {
monitorId: '1234',
body: '>>{"ip":"127.0.0.1"}<<',
},
};

// when
const response = await securityController.blockAccessOnBaleen(request);

// then
expect(response.output.statusCode).to.equal(400);
expect(response.message).to.equal('JA3 is mandatory.');
});
});

describe('when the ja3 parameter is empty', function () {
it('should return a 400 Bad Request', async function () {
// given
const request = {
headers: {
authorization: 'token',
},
payload: {
monitorId: '1234',
body: '>>{"ip": "127.0.0.1","ja3":""}<<',
},
};

// when
const response = await securityController.blockAccessOnBaleen(request);

// then
expect(response.output.statusCode).to.equal(400);
expect(response.message).to.equal('JA3 is mandatory.');
});
});
});
});

0 comments on commit c16a2f6

Please sign in to comment.