Skip to content
Open
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 apps/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ COPY --chown=jager:jager analyzer.py .
COPY --chown=jager:jager intrusion_detection.py .
COPY --chown=jager:jager utils.py .
COPY --chown=jager:jager coco.labels .
COPY --chown=jager:jager events.py .

CMD python3 analyzer.py
4 changes: 4 additions & 0 deletions deploy/templates/docker-compose.jin
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ services:
{%- endif %}
image: "jagereye/api:{{services.api.version}}"
network_mode: "{{services.api.network_mode}}"
cap_add:
{%- for cap in services.api.cap_add %}
- "{{cap}}"
{%- endfor %}
ports:
{%- for _, port in services.api.ports.items() %}
- "{{port}}"
Expand Down
4 changes: 3 additions & 1 deletion services/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ WORKDIR ${HOME}
# Install ffmpeg.
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg && \
ffmpeg net-tools sudo isc-dhcp-client iproute && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

COPY --chown=root:root ./settings_utils/sudoers /etc/
USER jager

# Create services structure
Expand Down
3 changes: 3 additions & 0 deletions services/api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { users, createAdminUser } = require('./users')
const analyzers = require('./analyzers')
const events = require('./events')
const helpers = require('./helpers')
const { settings, createDefaultNetworkSetting } = require('./settings')

const app = express()

Expand All @@ -22,6 +23,7 @@ app.use(API_ENTRY, users)
app.use(API_ENTRY, analyzers)
app.use(API_ENTRY, events)
app.use(API_ENTRY, helpers)
app.use(API_ENTRY, settings)

// Logging errors
app.use((err, req, res, next) => {
Expand All @@ -40,5 +42,6 @@ app.use((err, req, res, next) => {

// Create admin user if it is not existed
createAdminUser()
createDefaultNetworkSetting()

module.exports = app
166 changes: 166 additions & 0 deletions services/api/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
const express = require('express');
const router = express.Router();
const Ajv = require('ajv');
const P = require('bluebird');

const config = require('./config').services.api.settings;
const models = require('./database');
const settingsModel = P.promisifyAll(models['settings']);
const { createError } = require('./utils');
const { routesWithAuth } = require('./auth');
const { resetNetworkInterface, getInterfaceIp, ResetNetworkError } = require('./settings_utils');

const dataPortInterface = config.data_port.device;

const ajv = new Ajv();
const settingPatchSchema = {
'anyOf': [
{
type: 'object',
properties: {
mode: {
type: 'string',
pattern: '^static$',
},
address: {
type: 'string',
format: 'ipv4'
},
gateway: {
type: 'string',
format: 'ipv4'
},
netmask: {
type: 'string',
format: 'ipv4'
}
},
additionalProperties: false,
required: ['mode', 'address', 'gateway', 'netmask']
},
{
type: 'object',
properties: {
mode: {
type: 'string',
pattern: '^dhcp$',
},
},
additionalProperties: false,
required: ['mode']
},
]
}

const settingPatchValidator = ajv.compile(settingPatchSchema);

function validateSettingPatch(req, res, next) {
if(!settingPatchValidator(req.body)) {
// TODO: returned msg should be refine
return next(createError(400, settingPatchValidator.errors));
}
next();
}

async function patchSettings(req, res, next) {
let query = {}
let body = req.body

let mode = body.mode;
let addr = body.address;
let netmask = body.netmask;
let gateway = body.gateway;

let newSettings = {};
newSettings.mode = mode;
newSettings.address = addr;
newSettings.netmask = netmask;
newSettings.gateway = gateway;
newSettings.status = 'processing';
// First, insert record in db and response
await settingsModel.updateAsync({'_id': 1}, newSettings, {'upsert': true});
res.status(200).send();
// start configure network interface
try {
await resetNetworkInterface(dataPortInterface, mode, addr, netmask, gateway);
// update the status in db
newSettings.status = 'done';
// get the dhcp ip and update
if (mode === 'dhcp') {
let dhcp_address = getInterfaceIp(dataPortInterface);
if (!dhcp_address) {
throw new ResetNetworkError('dhcp failed');
}
newSettings.address = dhcp_address;
}
await settingsModel.updateAsync({'_id': 1}, newSettings, {'upsert': true});
} catch (err) {
newSettings.status = 'failed';
await settingsModel.updateAsync({'_id': 1}, newSettings, {'upsert': true});
// TODO: logging
console.error(err);
};
}

async function getSettings(req, res, next) {
let result = await settingsModel.findOne({'_id': 1});
if(result.mode === 'dhcp') {
result.netmask = undefined;
result.gateway = undefined;

// Ray: when users set dhcp without port connected,
// the port cannot get IP.
// whenever user connect the port, the port will receive IP soon.
// then the status of the port should be update

// on the other hand, whenever the port disconnected,
// the dhcp ip will be invalid, then the status should be updated

let dhcp_address = getInterfaceIp(dataPortInterface);
if (dhcp_address) {
result.address = dhcp_address;
result.status = 'done';
await settingsModel.updateAsync({'_id': 1}, result, {'upsert': true});
}
else {
result.address = 'None';
result.status = 'failed';
}
}
res.status(200).send(result);
}

async function createDefaultNetworkSetting() {
let result = await settingsModel.findOne({'_id': 1});
if(!result) {
// create default network setting
let defaultSettings = {};
defaultSettings.mode = 'dhcp';
defaultSettings.address = 'None';
defaultSettings.status = 'processing';
try {
await settingsModel.updateAsync({'_id': 1}, defaultSettings, {'upsert': true});
await resetNetworkInterface(dataPortInterface ,'dhcp');
} catch (err) {
defaultSettings.status = 'failed';
await settingsModel.updateAsync({'_id': 1}, defaultSettings, {'upsert': true});
// TODO: logging
console.error(err)
}
}
}


/*
* Routing Table
*/
routesWithAuth(
router,
['get', '/settings/networking', getSettings],
['patch', '/settings/networking', validateSettingPatch, patchSettings],
)

module.exports = {
settings: router,
createDefaultNetworkSetting
}
3 changes: 3 additions & 0 deletions services/api/settings_utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const resetNetworkInterface = require('./resetNetworkInterface.js')

module.exports = resetNetworkInterface;
53 changes: 53 additions & 0 deletions services/api/settings_utils/resetNetworkInterface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const os = require('os')
const P = require('bluebird');
const TimeoutError = P.TimeoutError;
const fs = P.promisifyAll(require('fs'));
const execAsync = P.promisify(require('child_process').exec);
const format = require('util').format;

const dataportInterfaceFilename = 'jagereye_dataport_interface';
const shellTimeout = 4000

class ResetNetworkError extends Error {
constructor (message, status) {
super(message);
this.name = this.constructor.name;
// Capturing stack trace, excluding constructor call from it.
Error.captureStackTrace(this, this.constructor);
this.status = status || 500;
}
};

async function resetNetworkInterface(networkInterface, mode, address, netmask, gateway) {
// flush the dataport ip
await execAsync(format('sudo ip addr flush dev %s', networkInterface), {timeout: shellTimeout});
if(mode === 'static') {
await execAsync(format('sudo ifconfig %s %s', networkInterface, address), {timeout: shellTimeout});
}
else if(mode === 'dhcp') {
try {
await execAsync(format('sudo dhclient %s', networkInterface), {timeout: shellTimeout});
} catch(e){
// it happened when the dataport cannot find out dhcp server
console.error(e);
throw new ResetNetworkError('dhcp failed');
}
}
}

function getInterfaceIp(interfaceName) {
let interfaces = os.networkInterfaces();
try {
let ip = interfaces[interfaceName][0]['address'];
return ip;
} catch(e) {
// TODO: logging
return;
}
}

module.exports = {
resetNetworkInterface: resetNetworkInterface,
getInterfaceIp: getInterfaceIp,
ResetNetworkError: ResetNetworkError
};
34 changes: 34 additions & 0 deletions services/api/settings_utils/sudoers
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults env_reset
Defaults mail_badpass
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"

# Host alias specification

# User alias specification

# Cmnd alias specification
Cmnd_Alias FLUSH_IP = /sbin/ip addr flush dev en*
Cmnd_Alias IFCONFIG = /sbin/ifconfig en* *
Cmnd_Alias DHCLIENT = /sbin/dhclient en*

# User privilege specification
root ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d
jager ALL=(ALL) NOPASSWD: FLUSH_IP, IFCONFIG, DHCLIENT
8 changes: 8 additions & 0 deletions shared/config.development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ services:
api:
version: "0.0.1"
network_mode: host
cap_add:
- NET_ADMIN
ports:
client: "5000"
base_url: "api/v1"
Expand All @@ -12,6 +14,12 @@ services:
token:
enabled: true
secret: "jagereye_dev"
settings:
control_port:
device: "enp1s0"
static_ip: "192.168.20.1"
data_port:
device: "enp2s0"
database:
version: "mongo-3.6.0"
db_name: jagereye-dev
Expand Down
11 changes: 11 additions & 0 deletions shared/database.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@
"source": "SUBDOC_SOURCE",
"pipelines": [ "SUBDOC_PIPELINES" ]
},
"settings": {
"_id": "Number",
"mode": {
"type": "string",
"required": true
},
"address": "string",
"netmask": "string",
"gateway": "string",
"status": "string"
},
"events": {
"type": {
"type": "string",
Expand Down