Skip to content

Commit

Permalink
Merge pull request #3 from digicatapult/feature/dsbd-31
Browse files Browse the repository at this point in the history
Feature/dsbd 31
  • Loading branch information
n3op2 authored Aug 3, 2022
2 parents f0742a8 + 2f9d605 commit 19f2db7
Show file tree
Hide file tree
Showing 15 changed files with 142 additions and 69 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ npm run build
npn run dev
```

#### SSH Configuration
> .ssh/known_hosts
> confirming
## Tests
So far we have two tests **unit** and **integration** each could be executed by calling the below commands
```sh
Expand Down
Binary file added bin/out-of-bounds-read-aarch64
Binary file not shown.
Binary file added bin/out-of-bounds-read-cheri
Binary file not shown.
Binary file added bin/out-of-bounds-write-aarch64
Binary file not shown.
Binary file added bin/out-of-bounds-write-cheri
Binary file not shown.
2 changes: 1 addition & 1 deletion config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"username": "MORELLO_USERNAME"
},
"app": {
"PORT": "PORT"
"port": "PORT"
}
}
3 changes: 2 additions & 1 deletion config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
},
"morello": {
"address": "127.0.0.1",
"username": "root"
"username": "root",
"port": 22
},
"app": {
"port": 3000
Expand Down
3 changes: 2 additions & 1 deletion config/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
},
"morello": {
"address": "100.100.100.100",
"username": "username"
"username": "username",
"port": 2222
}
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "@digicatapult/morello-api",
"version": "0.0.1",
"version": "0.1.0",
"description": "An interface for executing binaries on it's self and morello host.",
"main": "src/index.ts",
"scripts": {
"lint": "eslint ./src",
"build": "tsoa spec-and-routes && tsc",
"build": "tsoa spec-and-routes && tsc && cp -rv bin/ build/bin",
"start": "node build/index.js",
"depcheck": "depcheck --ignores='concurrently,nodemon'",
"dev": "concurrently \"npx tsc --watch\" \"nodemon -q build/index.js\"",
Expand Down
71 changes: 52 additions & 19 deletions src/controllers/scenario/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ const execute = async () => {
}
}

describe('/scenario controller', () => {
describe('/scenario/{example} endpoint', () => {
let res
let stubs = {}

beforeEach(async () => {
stubs.exec = stub(child, 'exec').yields(null, { stdout: 'ok' })
stubs.exec = stub(child, 'exec')
stubs.exec.onCall(0).yields(null, 'stdout - some output')
stubs.exec.onCall(1)
stubs.exec.onCall(2).yields(null, 'stdout - some output')
stubs.exec.onCall(3)
res = await execute()
})

Expand All @@ -28,37 +32,66 @@ describe('/scenario controller', () => {
describe('if executing binaries fails', () => {
beforeEach(async () => {
stubs.exec.restore()
stubs.exec = stub(child, 'exec').yields('fatal error')
stubs.exec = stub(child, 'exec')
stubs.exec.onCall(0).yields({ message: 'cheri - error' }, 'stdout - some output')
stubs.exec.onCall(1)
stubs.exec.onCall(2).yields({ message: 'aarch64 - error' }, 'stdout - some output')
stubs.exec.onCall(3)
res = await execute()
})

it('returns the correct state along with stderr', () => {
expect(res).to.include.all.keys(['morello', 'self'])
expect(res.morello).to.deep.equal({
output: 'fatal error',
it('returns correct state along with exceptions if both binaries fail', () => {
expect(res).to.include.all.keys(['aarch64', 'cheri'])
expect(res.aarch64).to.deep.equal({
status: 'error',
output: 'stdout - some output',
exception: { message: 'aarch64 - error' },
})
expect(res.self).to.deep.equal({
output: 'fatal error',
expect(res.cheri).to.deep.equal({
status: 'error',
output: 'stdout - some output',
exception: { message: 'cheri - error' },
})
})
})

it('exectures commands and returns formmated output', () => {
expect(res).to.include.all.keys(['morello', 'self'])
describe('and if some binaries fail and some succeed', () => {
beforeEach(async () => {
stubs.exec.restore()
stubs.exec = stub(child, 'exec')
stubs.exec.onCall(0).yields({ msg: 'fatal error' }, 'stdout - some output')
stubs.exec.onCall(1)
stubs.exec.onCall(2).yields(null, 'it was a success')
stubs.exec.onCall(3)
res = await execute()
})

it('returns formatted output with one with clear indication of failed ones', () => {
expect(res).to.include.all.keys(['aarch64', 'cheri'])
expect(res).to.deep.equal({
aarch64: {
status: 'success',
output: 'it was a success',
},
cheri: {
status: 'error',
output: 'stdout - some output',
exception: { msg: 'fatal error' },
},
})
})
})

it('returns a formatted output of both architectuures', () => {
expect(res).to.include.all.keys(['aarch64', 'cheri'])
expect(res).to.deep.equal({
morello: {
aarch64: {
status: 'success',
output: {
stdout: 'ok',
},
output: 'stdout - some output',
},
self: {
cheri: {
status: 'success',
output: {
stdout: 'ok',
},
output: 'stdout - some output',
},
})
})
Expand Down
62 changes: 38 additions & 24 deletions src/controllers/scenario/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,51 @@ import {
Get,
Path,
Route,
} from "tsoa";
} from 'tsoa'
import config from 'config'
import { exec } from 'child_process'
import { ExamplesResult } from '../../../types'
import { ExamplesResult, IScenario, HostResponse, Executables } from '../../../types'
import Logger from '../../utils/Logger'

let log = Logger.child({ controller: '/scenario', ...config.get('morello') });
const { address, username } = config.get('morello')

@Route('scenario')
export class scenario extends Controller {
@Get('{id}')
public async get(@Path() id: string): Promise<ExamplesResult> {
const cheriBin = `cat ./binaries/${id}-cheri | ssh ${username}@${address} "cat >${id}; chmod +x ${id}; ${id} with args; rm ${id}`
return ({
self: await new Promise((resolve) => {
log.info(`executing ${id} example on self`);
export class scenario extends Controller implements IScenario {
address: string
port: number
log: typeof Logger

constructor() {
super()
this.address = `${config.get('morello.username')}@${config.get('morello.address')}`
this.port = config.get('morello.port')
this.log = Logger.child({ controller: '/scenario', ...config.get('morello') })
}


exec(`./binaries/${id}-aarch64`, (err, stdout, stderr) => resolve({
status: (err || stderr) ? 'error': 'success',
output: err ? err : stderr ? stderr : stdout,
}))
}),
morello: await new Promise((resolve) => {
log.info(`executing ${id} exampole on the morello host`);
execute(bin: string): Promise<HostResponse> {
const scp = `scp -P ${this.port} bin/${bin} ${this.address}:/tmp`
const ssh = `ssh -p ${this.port} ${this.address} -tt 'chmod +x /tmp/${bin}; /tmp/${bin} with args' 2>&1`
const rm = `ssh -p ${this.port} ${this.address} rm -v /tmp/${bin} &> /dev/null`
this.log.debug({ msg: `executing ${bin} on ${this.address} host`, scp, ssh })

exec(cheriBin, (err, stdout, stderr) => resolve({
status: (err || stderr) ? 'error': 'success',
output: err ? err : stderr ? stderr : stdout,
}));
}),
return new Promise((resolve) => {
exec(`${scp}; ${ssh}`, (err, stdout) => {
exec(rm) // fire and forget, remove binary file
return resolve({
status: err ? 'error': 'success',
output: stdout,
...err ? { exception: err } : {}
})
})
})
}

@Get('{executable}')
public async get(@Path() executable: Executables): Promise<ExamplesResult> {
this.log.debug(`attempting to execute ${executable} scenario`)

return ({
cheri: await this.execute(`${executable}-cheri`),
aarch64: await this.execute(`${executable}-aarch64`),
})
}
}
20 changes: 10 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import config from 'config';
import { urlencoded, json} from 'body-parser';
import { urlencoded, json } from 'body-parser';
import * as swaggerUI from 'swagger-ui-express';
import express, { Express } from 'express';

Expand All @@ -8,16 +8,16 @@ import * as swaggerJson from './swagger.json';

import logger from './utils/Logger'

const app: Express = express();
const port: Number = config.get('app.port');
let log = logger.child({ example: "child-example" });
const app: Express = express()
const port: Number = config.get('app.port')
let log = logger.child({ example: "child-example" })

app.use(urlencoded({ extended: true }));
app.use(json());
RegisterRoutes(app);
app.use(['/openapi', '/docs', '/swagger'], swaggerUI.serve, swaggerUI.setup(swaggerJson));
app.use(urlencoded({ extended: true }))
app.use(json())
RegisterRoutes(app)
app.use(['/openapi', '/docs', '/swagger'], swaggerUI.serve, swaggerUI.setup(swaggerJson))
// TODO - errors and error handler

app.listen(port, () => {
log.info('Server is running');
log.info('Server is running')
});

12 changes: 10 additions & 2 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { ExamplesResult } from "./models/scenario";
import type {
ExamplesResult,
IScenario,
HostResponse,
Executables
} from "./models/scenario";

export type {
ExamplesResult
ExamplesResult,
IScenario,
Executables,
HostResponse,
}
25 changes: 18 additions & 7 deletions types/models/scenario.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { ExecException } from "child_process";
import { ExecException } from 'child_process'
import Logger from '../../src/utils/Logger'

// todo if it gets bigger group in models/ folder by entity and import here or ambient namescapes
type Response = {
status: 'success' | 'error',
output: string | ExecException,
export type Executables = 'out-of-bounds-read' | 'out-of-bounds-write'

export type HostResponse = {
status: 'success' | 'error' | 'exception',
output: string,
exception?: ExecException,
}

export interface IScenario {
readonly address: string
readonly port: number
log: typeof Logger
get: (executable: Executables) => Promise<ExamplesResult>
execute: (cmd: string) => Promise<HostResponse>
}

export interface ExamplesResult {
morello: Response,
self: Response,
aarch64: HostResponse,
cheri: HostResponse,
}

0 comments on commit 19f2db7

Please sign in to comment.