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 17, 2024
1 parent ed79627 commit 3d5e140
Show file tree
Hide file tree
Showing 7 changed files with 394 additions and 0 deletions.
3 changes: 3 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const configuration = (function () {
requestSigningSecret: process.env.SLACK_SIGNING_SECRET || 'slack-super-signing-secret',
botToken: process.env.SLACK_BOT_TOKEN,
webhookUrlForReporting: process.env.SLACK_WEBHOOK_URL_FOR_REPORTING,
blockedAccessesChannel: process.env.SLACK_BLOCKED_ACCESSES_CHANNEL,
},

github: {
Expand Down Expand Up @@ -171,6 +172,8 @@ const configuration = (function () {
config.baleen.appNamespaces = _getJSON('{"Pix_Test":"Pix_Namespace","Pix_Test_2":"Pix Namespace 2"}');
config.baleen.protectedFrontApps = ['Pix_Test'];

config.slack.blockedAccessesChannel = 'blocked-accesses-channel';

config.datadog.token = 'token';

config.github.token = undefined;
Expand Down
77 changes: 77 additions & 0 deletions run/controllers/security.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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 _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()}`),
)
.fallback('Règle de blocage mise en place sur Baleen.'),
)
.buildToObject().attachments,
};
};

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(_buildSlackMessage({ ip, ja3 }));
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;
8 changes: 8 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ SLACK_BOT_TOKEN=__CHANGE_ME__
# default: none
SLACK_WEBHOOK_URL_FOR_REPORTING=__CHANGE_ME__

# Slack "Pix Bot" channel to send baleen blocked accesses
#
# Channel name without the # before
# presence: required
# type: string
# default: none
SLACK_BLOCKED_ACCESSES_CHANNEL=__CHANGE_ME__

# ======================
# CDN MANAGEMENT
# ======================
Expand Down
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
138 changes: 138 additions & 0 deletions test/acceptance/run/security_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { config } from '../../../config.js';
import server from '../../../server.js';
import { expect, nock, sinon } from '../../test-helper.js';

describe('Acceptance | Run | Security', function () {
let now;
let clock;

beforeEach(function () {
now = new Date('2024-01-01');
clock = sinon.useFakeTimers({ now, toFake: ['Date'] });
});

afterEach(function () {
clock.restore();
});

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', {
reqheaders: {
'content-type': 'application/json',
authorization: `Bearer ${config.slack.botToken}`,
},
})
.post('/api/chat.postMessage', {
channel: `#${config.slack.blockedAccessesChannel}`,
text: 'Règle de blocage mise en place sur Baleen.',
attachments: [
{
color: '#106c1f',
blocks: [
{
fields: [
{
text: 'IP',
type: 'mrkdwn',
},
{
text: `${ip}`,
type: 'mrkdwn',
},
],
type: 'section',
},
{
fields: [
{
text: 'JA3',
type: 'mrkdwn',
},
{
text: `${ja3}`,
type: 'mrkdwn',
},
],
type: 'section',
},
{
elements: [
{
text: `At ${now.toLocaleString()}`,
type: 'mrkdwn',
},
],
type: 'context',
},
],
fallback: '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.');
expect(nock.isDone()).to.be.true;
});
});
});
Loading

0 comments on commit 3d5e140

Please sign in to comment.