Skip to content

Commit

Permalink
Host fingerprint (#26)
Browse files Browse the repository at this point in the history
* machine data is now being registered.

and a fingerprint is calculated using the registered data

* api version added
  • Loading branch information
facugon committed Mar 9, 2022
1 parent a589ae1 commit e9bc471
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 123 deletions.
1 change: 1 addition & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
file_upload_folder: join(__dirname , '..', 'uploads'),
view_teamplates_path: __dirname + '/../core/view/template',
secret: 'b28d9f2a4d52ace6e5d3ac1dd3e5c2a0e7e66472ec7276ca501b8c4fa1f07679',
secret_uuid: 'a2a29a19-aba1-412b-a9ba-c29668e3c17b',
user: {
username: 'theeye-automatic',
email: 'info@theeye.io',
Expand Down
193 changes: 150 additions & 43 deletions core/controllers/host.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const App = require('../app')
const restify = require('restify')
const config = require('config')
const App = require('../app')
const Constants = require('../constants')
const dbFilter = require('../lib/db-filter')
const Host = require("../entity/host").Entity
Expand All @@ -11,11 +11,13 @@ const NotificationService = require('../service/notification')
const Resource = require('../entity/resource').Entity
const router = require('../router')
const TopicsConstants = require('../constants/topics')
const machineFingerprint = require('../lib/machine-fingerprint')
const { ClientError, ServerError } = require('../lib/error-handler')

module.exports = function (server) {
const middlewares = [
server.auth.bearerMiddleware,
router.resolve.customerNameToEntity({ required: true }),
router.resolve.customerSessionToEntity({ required: true }),
router.ensureCustomer,
]

Expand All @@ -27,31 +29,38 @@ module.exports = function (server) {
controller.get
)

/**
* NEW ROUTES WITH CUSTOMER , TO KEEP IT GENERIC
*/
server.put('/:customer/host/:host/reconfigure',
middlewares,
router.resolve.idToEntity({ param: 'host', required: true }),
router.requireCredential('admin'),
controller.reconfigure
)

server.post('/:customer/host/:hostname',
middlewares,
router.requireCredential('agent',{exactMatch:true}), // only agents can create hosts
router.requireCredential('agent', { exactMatch: true }), // only agents can create hosts
controller.create
)

/**
* KEEP OLD ROUTE FOR BACKWARD COMPATIBILITY WITH OLDER AGENTS
*
* AGENTS VERSION <= v0.9.1
*/
server.post('/host/:hostname',
middlewares,
server.auth.bearerMiddleware,
router.resolve.customerSessionToEntity({ required: true }),
router.ensureCustomer,
router.requireCredential('agent', { exactMatch: true }), // only agents can create hosts
controller.create
)

server.put('/:customer/host/:host/reconfigure',
middlewares,
router.resolve.idToEntity({ param: 'host', required: true }),
router.requireCredential('admin'),
controller.reconfigure
//
// new agents registration process.
//
server.post('/host',
server.auth.bearerMiddleware,
router.resolve.customerSessionToEntity(),
router.ensureCustomer,
router.requireCredential('agent', { exactMatch: true }), // only agents can create hosts
restify.plugins.conditionalHandler([
{ version: '1.2.4', handler: controller.register }
])
)
}

Expand Down Expand Up @@ -102,6 +111,22 @@ const controller = {
})
})
},
async register (req, res, next) {
try {
const { user, customer, body } = req
const hostname = (req.params.hostname || req.body.hostname)
const data = await registerPullAgent({ user, customer, hostname, info: body.info })

const payload = Object.assign({}, config.agent.core_workers.host_ping)
payload.host_id = data.host._id
payload.resource_id = data.resource._id
payload.connection_id = data.connection.fingerprint

res.send(200, payload)
} catch (err) {
res.sendError(err)
}
},
/**
*
*
Expand All @@ -114,7 +139,7 @@ const controller = {

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

registerHostname(req, (error, result) => {
registerAgent(req, (error, result) => {
if (error) {
logger.error(error)
return res.send()
Expand Down Expand Up @@ -150,13 +175,14 @@ const controller = {
* @param {Function} done callback
* @return null
*/
const registerHostname = (req, done) => {
const registerAgent = (req, done) => {
const customer = req.customer
const hostname = req.params.hostname
const hostname = (req.params.hostname || req.body.hostname)
const body = req.body

// setting up registration properties
const properties = req.body.info || {}
properties.agent_version = req.body.version || null
const properties = body.info || {}
properties.agent_version = body.version || null

Host.findOne({
hostname,
Expand Down Expand Up @@ -210,26 +236,24 @@ const registerHostname = (req, done) => {
}

/** update agent reported version **/
function updateAgentVersion () {
logger.log('updating agent version')
host.agent_version = properties.agent_version
host.last_update = new Date()
host.save(err => {
if (err) {
logger.error(err)
return
}

const topic = TopicsConstants.agent.version
App.logger.submit(customer.name, topic, {
hostname,
organization: customer.name,
version: host.agent_version
}) // topic = topics.agent.version
})
}

updateAgentVersion()
//function updateAgentVersion () {
// logger.log('updating agent version')
// host.agent_version = properties.agent_version
// host.last_update = new Date()
// host.save(err => {
// if (err) {
// logger.error(err)
// return
// }
// const topic = TopicsConstants.agent.version
// App.logger.submit(customer.name, topic, {
// hostname,
// organization: customer.name,
// version: host.agent_version
// }) // topic = topics.agent.version
// })
//}
//updateAgentVersion()

Resource.findOne({
host_id: host._id,
Expand All @@ -251,3 +275,86 @@ const registerHostname = (req, done) => {
}
})
}

const registerPullAgent = async (input) => {
const { user, customer, info, hostname } = input

let resource
let host = await Host.findOne({
hostname,
customer_name: customer.name
})

if (!host) {
const result = await new Promise((resolve, reject) => {
HostService.register({
user,
hostname,
customer,
info
}, (err, result) => {
if (err) { reject(err) }
else { resolve(result) }
})
})

resource = result.resource
host = result.host

NotificationService.generateSystemNotification({
topic: TopicsConstants.host.registered,
data: {
model_type:'Host',
model: host,
model_id: host._id,
hostname,
organization: customer.name,
organization_id: customer._id,
operations: Constants.CREATE
}
})

HostService.provision({
host,
resource,
customer,
user,
skip_auto_provisioning: input.skip_auto_provisioning
})
} else {
resource = await Resource.findOne({
host_id: host._id,
type: 'host'
})

if (!resource) {
throw new ServerError('Host resource not found')
}
}

const connection = await registerHostFingerprint(host, info)

return { host, resource, connection }
}

const registerHostFingerprint = async (host, info) => {
const calc = machineFingerprint(info)

const registered = host.fingerprints.find(fp => {
return fp.fingerprint === calc
})

if (registered !== undefined) {
return registered
}

const fingerprint = Object.assign({}, info, {
fingerprint: calc,
creation_date: new Date()
})

host.fingerprints.push(fingerprint)
await host.save()

return fingerprint
}
71 changes: 41 additions & 30 deletions core/entity/host/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,9 @@ function HostSchema () {
const properties = {
disabled: { type: Boolean },
hostname: { type: String, index: true, required: true },
ip: { type: String },
os_name: { type: String },
os_version: { type: String },
agent_version: { type: String },
customer_name: { type: String, index: true },
customer_id: { type: String }, // Host customer_id is a String , will replace base-schema customer_id
//customer: { type: ObjectId, ref: 'Customer' },
integrations: {
type: IntegrationsSchema,
default: () => {
return {}
}
}
fingerprints: [ FingerprintSchema ]
}

// Schema constructor
Expand All @@ -33,26 +23,47 @@ function HostSchema () {
return this
}

const NgrokIntegrationSchema = new Schema({
active: { type: Boolean, default: false },
url: { type: String, default: '' },
last_update: { type: Date, default: Date.now },
last_job: {
type: mongoose.Schema.Types.ObjectId,
ref: 'NgrokIntegrationJob',
default: null
},
last_job_id: { type: String, default: '' }
},{ _id : false })

const IntegrationsSchema = new Schema({
ngrok: {
type: NgrokIntegrationSchema,
default: () => {
return {}
}
const FingerprintSchema = new Schema({
creation_date: Date,
fingerprint: String, // calculated data. can be recalculated using information below
platform: String,
hostname: String,
type: String,
release: String,
arch: String,
totalmem: String,
user: String,
cpu: [ Object ],
net: [ Object ],
cwd: String,
agent_version: String,
agent_username: String,
extras: {
user: Object,
agent_pid: String
}
},{ _id : false })
})

//const NgrokIntegrationSchema = new Schema({
// active: { type: Boolean, default: false },
// url: { type: String, default: '' },
// last_update: { type: Date, default: Date.now },
// last_job: {
// type: mongoose.Schema.Types.ObjectId,
// ref: 'NgrokIntegrationJob',
// default: null
// },
// last_job_id: { type: String, default: '' }
//},{ _id : false })
//
//const IntegrationsSchema = new Schema({
// ngrok: {
// type: NgrokIntegrationSchema,
// default: () => {
// return {}
// }
// }
//},{ _id : false })


util.inherits(HostSchema, BaseSchema)
Expand Down
32 changes: 32 additions & 0 deletions core/lib/machine-fingerprint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const config = require('config')
const { v5: uuidv5, v4: uuidv4 } = require('uuid')

const NAMESPACE = uuidv5(config.system.secret, config.system.secret_uuid)

module.exports = (machineData) => {
const payload = []
for (let name in machineData) {
const part = machineData[name]
if (typeof part === 'string') {
payload.push(part)
} else if (name === 'cpu' || name === 'net') {
// an array of available cpu/net
if (Array.isArray(part)) {
for (let idx = 0; idx < part.length; idx++) {
const device = part[idx]
if (name === 'cpu') {
payload.push(device.model)
// array of model, speed
}
else if (name === 'net') {
// array of name, address, mac
payload.push(device.name + device.addres + device.mac)
}
}
}
}
}
payload.sort()
const fingerprint = Buffer.from(payload.join('')).toString('base64')
return uuidv5(fingerprint, NAMESPACE)
}
Loading

0 comments on commit e9bc471

Please sign in to comment.