Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#389 - Validate Relay is verified when triggered #403

Merged
merged 2 commits into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');
});

});