Skip to content

Commit 0a4b415

Browse files
committed
Host fingerprint (#26)
* machine data is now being registered. and a fingerprint is calculated using the registered data * api version added
1 parent f7f1b4b commit 0a4b415

File tree

5 files changed

+232
-123
lines changed

5 files changed

+232
-123
lines changed

config/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = {
1313
file_upload_folder: join(__dirname , '..', 'uploads'),
1414
view_teamplates_path: __dirname + '/../core/view/template',
1515
secret: 'b28d9f2a4d52ace6e5d3ac1dd3e5c2a0e7e66472ec7276ca501b8c4fa1f07679',
16+
secret_uuid: 'a2a29a19-aba1-412b-a9ba-c29668e3c17b',
1617
user: {
1718
username: 'theeye-automatic',
1819

core/controllers/host.js

Lines changed: 150 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
'use strict'
21

3-
const App = require('../app')
2+
const restify = require('restify')
43
const config = require('config')
4+
const App = require('../app')
55
const Constants = require('../constants')
66
const dbFilter = require('../lib/db-filter')
77
const Host = require("../entity/host").Entity
@@ -11,11 +11,13 @@ const NotificationService = require('../service/notification')
1111
const Resource = require('../entity/resource').Entity
1212
const router = require('../router')
1313
const TopicsConstants = require('../constants/topics')
14+
const machineFingerprint = require('../lib/machine-fingerprint')
15+
const { ClientError, ServerError } = require('../lib/error-handler')
1416

1517
module.exports = function (server) {
1618
const middlewares = [
1719
server.auth.bearerMiddleware,
18-
router.resolve.customerNameToEntity({ required: true }),
20+
router.resolve.customerSessionToEntity({ required: true }),
1921
router.ensureCustomer,
2022
]
2123

@@ -27,31 +29,38 @@ module.exports = function (server) {
2729
controller.get
2830
)
2931

30-
/**
31-
* NEW ROUTES WITH CUSTOMER , TO KEEP IT GENERIC
32-
*/
32+
server.put('/:customer/host/:host/reconfigure',
33+
middlewares,
34+
router.resolve.idToEntity({ param: 'host', required: true }),
35+
router.requireCredential('admin'),
36+
controller.reconfigure
37+
)
38+
3339
server.post('/:customer/host/:hostname',
3440
middlewares,
35-
router.requireCredential('agent',{exactMatch:true}), // only agents can create hosts
41+
router.requireCredential('agent', { exactMatch: true }), // only agents can create hosts
3642
controller.create
3743
)
3844

39-
/**
40-
* KEEP OLD ROUTE FOR BACKWARD COMPATIBILITY WITH OLDER AGENTS
41-
*
42-
* AGENTS VERSION <= v0.9.1
43-
*/
4445
server.post('/host/:hostname',
45-
middlewares,
46+
server.auth.bearerMiddleware,
47+
router.resolve.customerSessionToEntity({ required: true }),
48+
router.ensureCustomer,
4649
router.requireCredential('agent', { exactMatch: true }), // only agents can create hosts
4750
controller.create
4851
)
4952

50-
server.put('/:customer/host/:host/reconfigure',
51-
middlewares,
52-
router.resolve.idToEntity({ param: 'host', required: true }),
53-
router.requireCredential('admin'),
54-
controller.reconfigure
53+
//
54+
// new agents registration process.
55+
//
56+
server.post('/host',
57+
server.auth.bearerMiddleware,
58+
router.resolve.customerSessionToEntity(),
59+
router.ensureCustomer,
60+
router.requireCredential('agent', { exactMatch: true }), // only agents can create hosts
61+
restify.plugins.conditionalHandler([
62+
{ version: '1.2.4', handler: controller.register }
63+
])
5564
)
5665
}
5766

@@ -102,6 +111,22 @@ const controller = {
102111
})
103112
})
104113
},
114+
async register (req, res, next) {
115+
try {
116+
const { user, customer, body } = req
117+
const hostname = (req.params.hostname || req.body.hostname)
118+
const data = await registerPullAgent({ user, customer, hostname, info: body.info })
119+
120+
const payload = Object.assign({}, config.agent.core_workers.host_ping)
121+
payload.host_id = data.host._id
122+
payload.resource_id = data.resource._id
123+
payload.connection_id = data.connection.fingerprint
124+
125+
res.send(200, payload)
126+
} catch (err) {
127+
res.sendError(err)
128+
}
129+
},
105130
/**
106131
*
107132
*
@@ -114,7 +139,7 @@ const controller = {
114139

115140
logger.log('processing hostname "%s" registration request', hostname)
116141

117-
registerHostname(req, (error, result) => {
142+
registerAgent(req, (error, result) => {
118143
if (error) {
119144
logger.error(error)
120145
return res.send()
@@ -150,13 +175,14 @@ const controller = {
150175
* @param {Function} done callback
151176
* @return null
152177
*/
153-
const registerHostname = (req, done) => {
178+
const registerAgent = (req, done) => {
154179
const customer = req.customer
155-
const hostname = req.params.hostname
180+
const hostname = (req.params.hostname || req.body.hostname)
181+
const body = req.body
156182

157183
// setting up registration properties
158-
const properties = req.body.info || {}
159-
properties.agent_version = req.body.version || null
184+
const properties = body.info || {}
185+
properties.agent_version = body.version || null
160186

161187
Host.findOne({
162188
hostname,
@@ -210,26 +236,24 @@ const registerHostname = (req, done) => {
210236
}
211237

212238
/** update agent reported version **/
213-
function updateAgentVersion () {
214-
logger.log('updating agent version')
215-
host.agent_version = properties.agent_version
216-
host.last_update = new Date()
217-
host.save(err => {
218-
if (err) {
219-
logger.error(err)
220-
return
221-
}
222-
223-
const topic = TopicsConstants.agent.version
224-
App.logger.submit(customer.name, topic, {
225-
hostname,
226-
organization: customer.name,
227-
version: host.agent_version
228-
}) // topic = topics.agent.version
229-
})
230-
}
231-
232-
updateAgentVersion()
239+
//function updateAgentVersion () {
240+
// logger.log('updating agent version')
241+
// host.agent_version = properties.agent_version
242+
// host.last_update = new Date()
243+
// host.save(err => {
244+
// if (err) {
245+
// logger.error(err)
246+
// return
247+
// }
248+
// const topic = TopicsConstants.agent.version
249+
// App.logger.submit(customer.name, topic, {
250+
// hostname,
251+
// organization: customer.name,
252+
// version: host.agent_version
253+
// }) // topic = topics.agent.version
254+
// })
255+
//}
256+
//updateAgentVersion()
233257

234258
Resource.findOne({
235259
host_id: host._id,
@@ -251,3 +275,86 @@ const registerHostname = (req, done) => {
251275
}
252276
})
253277
}
278+
279+
const registerPullAgent = async (input) => {
280+
const { user, customer, info, hostname } = input
281+
282+
let resource
283+
let host = await Host.findOne({
284+
hostname,
285+
customer_name: customer.name
286+
})
287+
288+
if (!host) {
289+
const result = await new Promise((resolve, reject) => {
290+
HostService.register({
291+
user,
292+
hostname,
293+
customer,
294+
info
295+
}, (err, result) => {
296+
if (err) { reject(err) }
297+
else { resolve(result) }
298+
})
299+
})
300+
301+
resource = result.resource
302+
host = result.host
303+
304+
NotificationService.generateSystemNotification({
305+
topic: TopicsConstants.host.registered,
306+
data: {
307+
model_type:'Host',
308+
model: host,
309+
model_id: host._id,
310+
hostname,
311+
organization: customer.name,
312+
organization_id: customer._id,
313+
operations: Constants.CREATE
314+
}
315+
})
316+
317+
HostService.provision({
318+
host,
319+
resource,
320+
customer,
321+
user,
322+
skip_auto_provisioning: input.skip_auto_provisioning
323+
})
324+
} else {
325+
resource = await Resource.findOne({
326+
host_id: host._id,
327+
type: 'host'
328+
})
329+
330+
if (!resource) {
331+
throw new ServerError('Host resource not found')
332+
}
333+
}
334+
335+
const connection = await registerHostFingerprint(host, info)
336+
337+
return { host, resource, connection }
338+
}
339+
340+
const registerHostFingerprint = async (host, info) => {
341+
const calc = machineFingerprint(info)
342+
343+
const registered = host.fingerprints.find(fp => {
344+
return fp.fingerprint === calc
345+
})
346+
347+
if (registered !== undefined) {
348+
return registered
349+
}
350+
351+
const fingerprint = Object.assign({}, info, {
352+
fingerprint: calc,
353+
creation_date: new Date()
354+
})
355+
356+
host.fingerprints.push(fingerprint)
357+
await host.save()
358+
359+
return fingerprint
360+
}

core/entity/host/schema.js

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,9 @@ function HostSchema () {
99
const properties = {
1010
disabled: { type: Boolean },
1111
hostname: { type: String, index: true, required: true },
12-
ip: { type: String },
13-
os_name: { type: String },
14-
os_version: { type: String },
15-
agent_version: { type: String },
1612
customer_name: { type: String, index: true },
1713
customer_id: { type: String }, // Host customer_id is a String , will replace base-schema customer_id
18-
//customer: { type: ObjectId, ref: 'Customer' },
19-
integrations: {
20-
type: IntegrationsSchema,
21-
default: () => {
22-
return {}
23-
}
24-
}
14+
fingerprints: [ FingerprintSchema ]
2515
}
2616

2717
// Schema constructor
@@ -33,26 +23,47 @@ function HostSchema () {
3323
return this
3424
}
3525

36-
const NgrokIntegrationSchema = new Schema({
37-
active: { type: Boolean, default: false },
38-
url: { type: String, default: '' },
39-
last_update: { type: Date, default: Date.now },
40-
last_job: {
41-
type: mongoose.Schema.Types.ObjectId,
42-
ref: 'NgrokIntegrationJob',
43-
default: null
44-
},
45-
last_job_id: { type: String, default: '' }
46-
},{ _id : false })
47-
48-
const IntegrationsSchema = new Schema({
49-
ngrok: {
50-
type: NgrokIntegrationSchema,
51-
default: () => {
52-
return {}
53-
}
26+
const FingerprintSchema = new Schema({
27+
creation_date: Date,
28+
fingerprint: String, // calculated data. can be recalculated using information below
29+
platform: String,
30+
hostname: String,
31+
type: String,
32+
release: String,
33+
arch: String,
34+
totalmem: String,
35+
user: String,
36+
cpu: [ Object ],
37+
net: [ Object ],
38+
cwd: String,
39+
agent_version: String,
40+
agent_username: String,
41+
extras: {
42+
user: Object,
43+
agent_pid: String
5444
}
55-
},{ _id : false })
45+
})
46+
47+
//const NgrokIntegrationSchema = new Schema({
48+
// active: { type: Boolean, default: false },
49+
// url: { type: String, default: '' },
50+
// last_update: { type: Date, default: Date.now },
51+
// last_job: {
52+
// type: mongoose.Schema.Types.ObjectId,
53+
// ref: 'NgrokIntegrationJob',
54+
// default: null
55+
// },
56+
// last_job_id: { type: String, default: '' }
57+
//},{ _id : false })
58+
//
59+
//const IntegrationsSchema = new Schema({
60+
// ngrok: {
61+
// type: NgrokIntegrationSchema,
62+
// default: () => {
63+
// return {}
64+
// }
65+
// }
66+
//},{ _id : false })
5667

5768

5869
util.inherits(HostSchema, BaseSchema)

core/lib/machine-fingerprint.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const config = require('config')
2+
const { v5: uuidv5, v4: uuidv4 } = require('uuid')
3+
4+
const NAMESPACE = uuidv5(config.system.secret, config.system.secret_uuid)
5+
6+
module.exports = (machineData) => {
7+
const payload = []
8+
for (let name in machineData) {
9+
const part = machineData[name]
10+
if (typeof part === 'string') {
11+
payload.push(part)
12+
} else if (name === 'cpu' || name === 'net') {
13+
// an array of available cpu/net
14+
if (Array.isArray(part)) {
15+
for (let idx = 0; idx < part.length; idx++) {
16+
const device = part[idx]
17+
if (name === 'cpu') {
18+
payload.push(device.model)
19+
// array of model, speed
20+
}
21+
else if (name === 'net') {
22+
// array of name, address, mac
23+
payload.push(device.name + device.addres + device.mac)
24+
}
25+
}
26+
}
27+
}
28+
}
29+
payload.sort()
30+
const fingerprint = Buffer.from(payload.join('')).toString('base64')
31+
return uuidv5(fingerprint, NAMESPACE)
32+
}

0 commit comments

Comments
 (0)