288 lines
8.0 KiB
JavaScript
288 lines
8.0 KiB
JavaScript
const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
|
|
const { expect } = require('chai');
|
|
const sinon = require('sinon');
|
|
const IPRestrictionMiddleware = require('../../middleware/ip-restriction');
|
|
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, mockRequest, mockResponse, mockNext, createTestUser, createTestTenant } = require('../setup');
|
|
|
|
describe('IP Restriction Middleware', () => {
|
|
let models, sequelize, ipRestriction;
|
|
|
|
before(async () => {
|
|
({ models, sequelize } = await setupTestEnvironment());
|
|
ipRestriction = new IPRestrictionMiddleware();
|
|
ipRestriction.setModels(models);
|
|
});
|
|
|
|
after(async () => {
|
|
await teardownTestEnvironment();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await cleanDatabase();
|
|
});
|
|
|
|
describe('checkIPRestriction', () => {
|
|
it('should allow access when IP restrictions disabled', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: false,
|
|
ip_whitelist: '192.168.1.1,10.0.0.1'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '127.0.0.1',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should allow access from allowed IP', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '192.168.1.1,10.0.0.1,127.0.0.1'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '192.168.1.1',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should block access from non-allowed IP', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '192.168.1.1,10.0.0.1'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '192.168.2.1',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(403);
|
|
expect(res.data).to.have.property('success', false);
|
|
expect(res.data).to.have.property('message');
|
|
expect(res.data).to.have.property('code', 'IP_RESTRICTED');
|
|
expect(res.data).to.have.property('timestamp');
|
|
});
|
|
|
|
it('should allow access when tenant not found', async () => {
|
|
const req = mockRequest({
|
|
ip: '192.168.1.1',
|
|
tenant: 'nonexistent-tenant'
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should extract IP from x-forwarded-for header', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '203.0.113.1'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '127.0.0.1', // Local proxy IP
|
|
headers: { 'x-forwarded-for': '203.0.113.1, 198.51.100.1' },
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should extract IP from x-real-ip header', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '203.0.113.2'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '127.0.0.1',
|
|
headers: { 'x-real-ip': '203.0.113.2' },
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should handle CIDR notation in allowed IPs', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '192.168.1.0/24,10.0.0.0/8'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '192.168.1.100',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should block IP outside CIDR range', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '192.168.1.0/24'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '192.168.2.1',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(403);
|
|
});
|
|
|
|
it('should allow access from Docker container networks', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '192.168.1.1'
|
|
});
|
|
|
|
const dockerIPs = ['172.17.0.1', '172.18.0.1', '172.19.0.1'];
|
|
|
|
for (const ip of dockerIPs) {
|
|
const req = mockRequest({
|
|
ip: ip,
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
expect(next.errors).to.have.length(0);
|
|
}
|
|
});
|
|
|
|
it('should allow management routes regardless of IP restrictions', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
allowed_ips: '192.168.1.1'
|
|
});
|
|
|
|
const managementPaths = [
|
|
'/api/management/status',
|
|
'/api/management/health',
|
|
'/api/management/system-info'
|
|
];
|
|
|
|
for (const path of managementPaths) {
|
|
const req = mockRequest({
|
|
ip: '192.168.2.1', // Not in allowed IPs
|
|
path: path,
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
expect(next.errors).to.have.length(0);
|
|
}
|
|
});
|
|
|
|
it('should handle empty ip_whitelist', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: ''
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '192.168.1.1',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(403);
|
|
});
|
|
|
|
it('should handle null ip_whitelist', async () => {
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: null
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '192.168.1.1',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(403);
|
|
});
|
|
|
|
it('should log IP restriction events', async () => {
|
|
const consoleLogSpy = sinon.spy(console, 'log');
|
|
|
|
const tenant = await createTestTenant({
|
|
slug: 'test-tenant',
|
|
ip_restriction_enabled: true,
|
|
ip_whitelist: '192.168.1.1'
|
|
});
|
|
|
|
const req = mockRequest({
|
|
ip: '192.168.2.1',
|
|
tenant: tenant.slug
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
await ipRestriction.checkIPRestriction(req, res, next);
|
|
|
|
expect(consoleLogSpy.calledWith(sinon.match(/\[SECURITY AUDIT\].*denied access to/))).to.be.true;
|
|
|
|
consoleLogSpy.restore();
|
|
});
|
|
});
|
|
});
|