Skip to content

Commit

Permalink
#389 - Validate Relay is verified when triggered (#403)
Browse files Browse the repository at this point in the history
* #389 - Validate Relay is verified when triggered
* Fixed CI to skip Relay verification
  • Loading branch information
petruki authored May 7, 2023
1 parent 379a155 commit aa1905f
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 39 deletions.
1 change: 1 addition & 0 deletions .env-cmdrc-template
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"JWT_ADMIN_TOKEN_RENEW_INTERVAL": "5m",
"MAX_STRATEGY_OPERATION": 100,
"RELAY_BYPASS_HTTPS": true,
"RELAY_BYPASS_VERIFICATION": true,
"HISTORY_ACTIVATED": true,
"METRICS_ACTIVATED": true,
"METRICS_MAX_PAGE": 50,
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
GOOGLE_SKIP_AUTH: false
MAX_STRATEGY_OPERATION: 100
RELAY_BYPASS_HTTPS: true
RELAY_BYPASS_VERIFICATION: true
METRICS_ACTIVATED: true
METRICS_MAX_PAGE: 50
SWITCHER_API_ENABLE: false
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
GOOGLE_SKIP_AUTH: false
MAX_STRATEGY_OPERATION: 100
RELAY_BYPASS_HTTPS: true
RELAY_BYPASS_VERIFICATION: true
METRICS_ACTIVATED: true
METRICS_MAX_PAGE: 50
SWITCHER_API_ENABLE: false
Expand Down
1 change: 1 addition & 0 deletions config/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ JWT_CLIENT_TOKEN_EXP_TIME=5m
JWT_ADMIN_TOKEN_RENEW_INTERVAL=10m
MAX_STRATEGY_OPERATION=100
RELAY_BYPASS_HTTPS=true
RELAY_BYPASS_VERIFICATION=true
REGEX_MAX_TIMEOUT=3000
REGEX_MAX_BLACLIST=50
HISTORY_ACTIVATED=true
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ services:
- JWT_ADMIN_TOKEN_RENEW_INTERVAL=${JWT_ADMIN_TOKEN_RENEW_INTERVAL}
- MAX_STRATEGY_OPERATION=${MAX_STRATEGY_OPERATION}
- RELAY_BYPASS_HTTPS=${RELAY_BYPASS_HTTPS}
- RELAY_BYPASS_VERIFICATION=${RELAY_BYPASS_VERIFICATION}
- HISTORY_ACTIVATED=${HISTORY_ACTIVATED}
- METRICS_ACTIVATED=${METRICS_ACTIVATED}
- METRICS_MAX_PAGE=${METRICS_MAX_PAGE}
Expand Down
1 change: 1 addition & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ app.get('/check', (_req, res) => {
switcherapi: process.env.SWITCHER_API_ENABLE,
switcherapi_logger: process.env.SWITCHER_API_LOGGER,
relay_bypass_https: process.env.RELAY_BYPASS_HTTPS,
relay_bypass_verification: process.env.RELAY_BYPASS_VERIFICATION,
history: process.env.HISTORY_ACTIVATED,
metrics: process.env.METRICS_ACTIVATED,
max_metrics_pages: process.env.METRICS_MAX_PAGE,
Expand Down
34 changes: 22 additions & 12 deletions src/client/relay/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import axios from 'axios';
import { StrategiesToRelayDataType, RelayMethods } from '../../models/config';

export function resolveNotification(url, method, entry, auth_prefix, auth_token) {
if (method === RelayMethods.GET) {
get(url, createParams(entry), createHeader(auth_prefix, auth_token));
export function resolveNotification(relay, entry, environment) {
const url = relay.endpoint[environment];
const header = createHeader(relay.auth_prefix, relay.auth_token, environment);

if (relay.method === RelayMethods.GET) {
get(url, createParams(entry), header);
} else {
post(url, createBody(entry), createHeader(auth_prefix, auth_token));
post(url, createBody(entry), header);
}
}

export async function resolveValidation(url, method, entry, auth_prefix, auth_token) {
export async function resolveValidation(relay, entry, environment) {
let response;
if (method === RelayMethods.GET) {
response = await get(url, createParams(entry), createHeader(auth_prefix, auth_token));

const url = relay.endpoint[environment];
const header = createHeader(relay.auth_prefix, relay.auth_token, environment);

if (relay.method === RelayMethods.GET) {
response = await get(url, createParams(entry), header);
} else {
response = await post(url, createBody(entry), createHeader(auth_prefix, auth_token));
response = await post(url, createBody(entry), header);
}

return {
Expand Down Expand Up @@ -56,13 +63,16 @@ function createParams(entry) {
return '';
}

function createHeader(auth_prefix, auth_token) {
function createHeader(auth_prefix, auth_token, environment) {
let headers = {};

headers['Content-Type'] = 'application/json';
if (auth_prefix)
headers['Authorization'] = `${auth_prefix} ${auth_token}`;


if (auth_token && environment in auth_token &&
auth_prefix && auth_token[environment]) {
headers['Authorization'] = `${auth_prefix} ${auth_token[environment]}`;
}

return {
headers
};
Expand Down
15 changes: 7 additions & 8 deletions src/client/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { verifyOwnership } from '../helpers';
import { resolveNotification, resolveValidation } from './relay/index';
import Component from '../models/component';
import Logger from '../helpers/logger';
import { validateRelay } from '../services/config';
import { isRelayVerified, isRelayValid } from '../services/config';

export const resolveConfigByKey = async (domain, key) => Config.findOne({ domain, key }, null, { lean: true });

Expand Down Expand Up @@ -173,15 +173,14 @@ async function checkConfigStrategies(configId, strategyFilter) {
async function resolveRelay(config, environment, entry, response) {
try {
if (config.relay?.activated[environment]) {
validateRelay(config.relay);
isRelayValid(config.relay);
isRelayVerified(config.relay);

if (config.relay.type === RelayTypes.NOTIFICATION) {
resolveNotification(config.relay.endpoint[environment], config.relay.method, entry,
config.relay.auth_prefix, config.relay.auth_token[environment]);
resolveNotification(config.relay, entry, environment);
} else {
const relayResponse = await resolveValidation(config.relay.endpoint[environment], config.relay.method, entry,
config.relay.auth_prefix, config.relay.auth_token[environment]);

const relayResponse = await resolveValidation(config.relay, entry, environment);

response.result = relayResponse.result;
response.reason = relayResponse.result ? 'Success' : 'Relay does not agree';
response.message = relayResponse.message;
Expand All @@ -190,7 +189,7 @@ async function resolveRelay(config, environment, entry, response) {
} catch (e) {
if (config.relay.type === RelayTypes.VALIDATION) {
response.result = false;
response.reason = 'Relay service could not be reached';
response.reason = `Relay service could not be reached: ${e.message}`;
Logger.error(response.reason, e);
}
}
Expand Down
14 changes: 12 additions & 2 deletions src/services/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export async function updateConfig(id, args, admin) {

export async function updateConfigRelay(id, args, admin) {
let config = await getConfigById(id);
validateRelay(args);
isRelayValid(args);

config = await verifyOwnership(admin, config, config.domain, ActionTypes.UPDATE, RouterTypes.CONFIG);
config.updatedBy = admin.email;
Expand Down Expand Up @@ -286,7 +286,7 @@ export async function verifyRelay(id, code, admin) {
return 'failed';
}

export function validateRelay(relay) {
export function isRelayValid(relay) {
const bypass = process.env.RELAY_BYPASS_HTTPS === 'true' || false;

if (bypass || !relay.endpoint)
Expand All @@ -297,4 +297,14 @@ export function validateRelay(relay) {

if (foundNotHttps.length)
throw new BadRequestError('HTTPS required');
}

export function isRelayVerified(relay) {
const bypass = process.env.RELAY_BYPASS_VERIFICATION === 'true' || false;

if (bypass)
return;

if (!relay.verified)
throw new BadRequestError('Relay not verified');
}
134 changes: 117 additions & 17 deletions tests/relay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import request from 'supertest';
import sinon from 'sinon';
import axios from 'axios';
import app from '../src/app';
import { RelayMethods, RelayTypes } from '../src/models/config';
import { Config, RelayMethods, RelayTypes } from '../src/models/config';
import {
setupDatabase,
adminMasterAccountToken,
Expand All @@ -27,6 +27,19 @@ const changeStrategy = async (strategyId, newOperation, status, environment) =>
await strategy.save();
};

const bodyRelay = (endpoint) => {
return {
type: RelayTypes.VALIDATION,
activated: {
default: true
},
endpoint: {
default: endpoint
},
method: RelayMethods.GET
};
};

beforeAll(setupDatabase);

afterAll(async () => {
Expand Down Expand Up @@ -295,7 +308,7 @@ describe('Testing Switcher Relay', () => {

axiosStub.restore();
expect(req.statusCode).toBe(200);
expect(req.body.reason).toEqual('Relay service could not be reached');
expect(req.body.reason).toEqual('Relay service could not be reached: Failed to reach http://localhost:3001 via GET');
expect(req.body.result).toBe(false);
});

Expand All @@ -318,27 +331,14 @@ describe('Testing Switcher Relay', () => {

axiosStub.restore();
expect(req.statusCode).toBe(200);
expect(req.body.reason).toEqual('Relay service could not be reached');
expect(req.body.reason).toEqual('Relay service could not be reached: Failed to reach http://localhost:3001 via POST');
expect(req.body.result).toBe(false);
});

});

describe('Testing Switcher Relay Validation', () => {

const bodyRelay = (endpoint) => {
return {
type: RelayTypes.VALIDATION,
activated: {
default: true
},
endpoint: {
default: endpoint
},
method: RelayMethods.GET
};
};

let token;

beforeAll(async () => {
Expand Down Expand Up @@ -389,7 +389,107 @@ describe('Testing Switcher Relay Validation', () => {
]});

expect(req.statusCode).toBe(200);
expect(req.body.reason).toEqual('Relay service could not be reached');
expect(req.body.reason).toEqual('Relay service could not be reached: HTTPS required');
});

});

describe('Testing Switcher Relay Verification', () => {

let token;
let axiosStub;

beforeAll(async () => {
const response = await request(app)
.post('/criteria/auth')
.set('switcher-api-key', `${apiKey}`)
.send({
domain: domainDocument.name,
component: component1.name,
environment: EnvType.DEFAULT
}).expect(200);

token = response.body.token;
});

afterAll(() => {
process.env.RELAY_BYPASS_VERIFICATION = true;
});

test('RELAY_SUITE - Should return Relay could not be reached - Not verified', async () => {
// Given
// Verification required
process.env.RELAY_BYPASS_VERIFICATION = false;

// Setup Switcher
await request(app)
.patch(`/config/updateRelay/${configId}`)
.set('Authorization', `Bearer ${adminMasterAccountToken}`)
.send(bodyRelay('https://localhost:3001')).expect(200);

// Test
const req = await request(app)
.post(`/criteria?key=${keyConfig}&showReason=true&showStrategy=true`)
.set('Authorization', `Bearer ${token}`)
.send({
entry: [
{
strategy: StrategiesType.VALUE,
input: 'USER_1'
},
{
strategy: StrategiesType.NETWORK,
input: '10.0.0.3'
}
]});

expect(req.statusCode).toBe(200);
expect(req.body.reason).toEqual('Relay service could not be reached: Relay not verified');
});

test('RELAY_SUITE - Should return success when validating verified relay', async () => {
// Mock
axiosStub = sinon.stub(axios, 'get');

// Given
const mockRelayService = { data: { result: true, reason: 'Success' } };
axiosStub.returns(Promise.resolve(mockRelayService));

// Verification required
process.env.RELAY_BYPASS_VERIFICATION = false;

// Setup Switcher
await request(app)
.patch(`/config/updateRelay/${configId}`)
.set('Authorization', `Bearer ${adminMasterAccountToken}`)
.send(bodyRelay('https://localhost:3001')).expect(200);

// Config has a verified Relay
const config = await Config.findById(configId).exec();
config.relay.verified = true;
config.relay.verification_code = '123';
await config.save();
expect(config.relay.verified).toBe(true);

// Test
const req = await request(app)
.post(`/criteria?key=${keyConfig}&showReason=true&showStrategy=true`)
.set('Authorization', `Bearer ${token}`)
.send({
entry: [
{
strategy: StrategiesType.VALUE,
input: 'USER_1'
},
{
strategy: StrategiesType.NETWORK,
input: '10.0.0.3'
}
]});

axiosStub.restore();
expect(req.statusCode).toBe(200);
expect(req.body.reason).toEqual('Success');
});

});

0 comments on commit aa1905f

Please sign in to comment.