wot-testbench v1.1.2
Web of Things Test Bench
Tests a WoT Thing by executing interactions automatically, based on its Thing Description.
A Thing Description should represent capabilities of a device. This implies that if a device support the interactions that a client can execute based on the device's TD, it doesn't comply to its own TD. Test bench tests whether:
- Every interaction written in the TD can be executed
- Writable properties are indeed writable
- Each interaction returns the described data type (DataSchema of TD Spec)
Installation
Prerequisites:
- Node.js:
sudo apt-get install -y nodejs(currently version 20 is not supported) - Typescript:
npm install -g typescript - ts-node:
npm install -g ts-node
Steps
- Clone testbench from its repository by
git clone git@github.com:tum-esi/testbench.git - Switch into
testbenchfolder - Execute the
npm install. This will install every required library, includingnode-wot - Execute
npm run-script build
Example Usage
Quick Method with Default Configuration
Start a servient that has a TD so that TestBench can interact with it.
testing-files/faultyThing.tsshows an example test servient with ONLY BAD implementations. RunfaultyThing.tsby executingts-node testing-files/faultyThing.tsinsidetestbenchdirectory.testing-files/perfectThing.tsshows an example test servient with ONLY GOOD implementations. RunperfectThing.tsby executingts-node testing-files/perfectThing.tsinsidetestbenchdirectory.
Run with:
npm startInteract with the testbench using REST clients such as
cURL,Postmanetc.- Test a servient by sending its TD
| POST | Test Thing with given TD |
|---|---|
| content-type | application/json |
| body | Thing Description |
| data-type | raw |
| url | http://your-address:8980/wot-test-bench/actions/fastTest |
| return value | JSON Array with results |
TestBench is a WoT Thing itself with a TD, so you can interact with it like you interact with other WoT servients.
Postman:
| PUT | TestBench config update |
|---|---|
| content-type | application/json |
| body | config json data |
| data-type | raw |
| url | http://your-address:8980/wot-test-bench/properties/testConfig |
cURL:
curl -X POST -H "Content-Type: application/json" -d '{configuration-data}' http://your-address:8980/wot-test-bench/properties/testConfig
IMPORTANT: fastTest does two things:
1. calls testThing and sets result of this action to the value of conformance key in the testReport.
- calls
testVulnerabilitiesand sets result of this action to the value ofvulnerabilitieskey in thetestReport.
Method with all Customization Options
Testing for Conformance
In conformance test, the TestBench sends valid requests to the Thing, and validates responses, via testThing action.
Start a servient that has a TD so that TestBench can interact with it.
testing-files/faultyThing.tsshows an example test servient with ONLY BAD implementations. RunfaultyThing.tsby executingts-node testing-files/faultyThing.tsinsidetestbenchdirectory.testing-files/perfectThing.tsshows an example test servient with ONLY GOOD implementations. RunperfectThing.tsby executingts-node testing-files/perfectThing.tsinsidetestbenchdirectory.
Run the TestBench by executing
npm start.- Before doing so, you can configure the test bench by changing the
defaultConfiginsidedefaults.tsfile.
- Before doing so, you can configure the test bench by changing the
Start
Postmansoftware: PostmanSend the TD of the Thing you want to test by writing into the
thingUnderTestTDpropertyfaultyThing.tscreates a TD for itself after it has run. Runcurl http://localhost:8083/faulty-thing-servientto get TD. Warning!: Ports might cause an error, so either make sure port numbers inside thefaultyThing.tsfile are available or change them.
| PUT | TestBench update TuT Property |
|---|---|
| content-type | application/json |
| body | Thing Description |
| data-type | raw |
| url | http://your-address:8980/wot-test-bench/properties/thingUnderTestTD |
| return value: | no return value |
- (Optional) Update the test configuration by writing to the
testConfigproperty.- This is not optional if you have to add security configuration. You should resend the test configuration with the credentials filled according to the Thing you want to test, like in the following example:
"credentials": {
THING_ID1: {
"token": TOKEN
},
THING_ID2: {
"username": USERNAME,
"password": PASSWORD
}
}- Call initialization sequence of the TestBench by invoking the
initiateaction. This is where TestBench reads new configurations, consumes the provided TD of Thing under Test and exposes generatedtestDatawhich is sent during testing procedure as a property of TestBench. Input data"true"activates logging to console which can show detailed error logs.
| POST | TestBench initiation |
|---|---|
| content-type | application/json |
| body | boolean |
| data-type | raw |
| url | http://your-address:8980/wot-test-bench/actions/initiate |
| return value: | boolean if successful |
- (Optional) Change the data that will be sent to the Thing under Test by writing to the
testDataproperty.
| PUT | TestBench change request data |
|---|---|
| content-type | application/json |
| body | [{"interactionName":"testObject","interactionValue":{"brightness":50,"status":"my change"}},{"interactionName":"testObject","interactionValue":{"brightness":41.447134566914734,"status":"ut aut"}},[{"interactionName":"testArray","interactionValue":87987366.27759776,18277015.91254884,-25996637.898988828,-31082548.946999773},{"interactionName":"testArray","interactionValue":2907339.2741234154,-24383724.353494212}],{"interactionName":"display","interactionValue":"eu ad laborum"}, ... , ... ] |
| data-type | raw |
| url | http://your-address:8980/wot-test-bench/actions/updateRequests |
| return value: | no return value |
- Test the configured Thing by invoking
testThingaction. Test bench reads the testData property and executes testing procedure on consumed Thing. Then, it exposes a test report. Body set to"true"activates logging to console.
| POST | TestBench execute action testThing |
|---|---|
| content-type | application/json |
| body | "true" |
| data-type | raw |
| url | http://your-address:8980/wot-test-bench/actions/testThing |
| return value: | boolean if successful |
- Read the test report by reading the testReport property.
Testing for Coverage
The action, testAllLevels, tests for different levels of coverage. Levels include Operation, Parameter, Input and Output.
- Test a servient for all coverage levels by sending its TD
| POST | Test Thing with given TD |
|---|---|
| content-type | application/json |
| body | Thing Description |
| data-type | raw |
| url | http://your-address:8980/wot-test-bench/actions/testAllLevels |
| return value | JSON Array with results |
Testing for Vulnerabilities
The action, testVulnerabilities, tests for vulnerabilities, both from security and safety perspectives.
The main motivation behind this action is to cover the security of the Thing. From pre-determined sets of usernames and passwords, this action involves performing penetration testing with dictionary attacks. It also performs some common safety tests similar to those done under testThing.
Perform steps 1 - 6 as described above, then send a POST request with a body containing true or false (indicating whether to perform a relatively faster and less covering test or not, true: a small subset of all combinations, false: all combinations) to http://your-address:8980/wot-test-bench/actions/testVulnerabilities. Read the test report by sending a GET request to http://your-address:8980/wot-test-bench/properties/testReport.
Structure of the Vulnerability Report
Mainly consists of two parts: propertyReports containing reports of properties, and actionReports containing those of actions.
Each one of these reports consists of:
propertyName/actionNameto distinguish from other properties/actions.security, which containspassedDictionaryAttackindicating whether the credentials needed to access this property/action (directly or indirectly) are found in the pre-determined username-password combinations. true unless a suitable pair of credentials are found by dictionary attack.description, used for human readability purposes.optional
idandpw: these two contains the username and password ifpassedDictionaryAttackisfalse.
safety, which contains(if property report)
isReadableindicating whether this property can be read. Must be compared with the property in the TD. In an non-erroneous case, they should match. e.g. for awriteOnlyproperty this should be false.(if property report)
isWritableindicating whether this property can be written. Must be compared with the property in the TD. In an non-erroneous case, they should match. e.g. for areadOnlyproperty this should be false.exceptionTypesindicating the types that should not be optimally accepted. If not empty, then those types are accepted by the property/action, you should check for those exception types in your implementation.
Notes
ALWAYS call
initiateafter writingthingUnderTestTDif you are going to performtestThingortestVulnerabilitiesonly.Depending on where the Thing is hosted and number of
InteractionAffordances, this test may take quite some time.- Currently only supports HTTP/HTTPs.
- Currently only supports
basicandoauth2withclient_credentialsflow. - Dictionary attacks are performed on the Thing in case of
basicsecurity scheme, and on thetokenserver in case ofoauth2. - In this README, the phrase types that should not be optimally allowed is frequently used and those are the types that are not given in the TD, which should be optimally avoided on the implementation side. e.g. a
booleanfor anumbertype of property. - If the
InteractionAffordancehas a different security scheme than the one undersecurityof the TD,testVulnerabilitiesthrows.
This link provides all possible postman interaction examples https://documenter.getpostman.com/view/4378601/RWEmHGBq.
How to use testbench screencast video can be found here https://youtu.be/BDMbXZ2O7KI.
You can use your browser and the GET requests to inspect all properties during the procedure.
How does the conformance testing work?
- During the whole testing process every step is logged in the CLI if logMode is enabled. Additionally any sent or received Data is written into the test report together with an analysis of the process.
- The testing process consists of four stages:
Starting Phase
- The testbench extracts the schemas of the different interactions (properties, actions and events) from the TD provided in the payload of the GET request.
- It then generates random requests that match these extracted schemas.
Main Phase
Now every interaction is tested sequentially. This asynchronus testing leeds to a easily readable log.
Actions
- The testbench sends a request matching the input specified in the TD.
- The testbench verifies the actual output against the output specified in the TD.
Properties:
- The reading functionality is tested by sending the specified request for readProperty to the Thing and verifying the output against output specified in the TD.
- The writing functionality is tested by sending the specified request for writeProperty to the Thing. Afterwards the testbench tries to read the property again and checks if the read matched the write. A non matching read is still counted as a pass, due to the fact that the property could have just changed between the write and read request.
- For observing functionality see Events just with observeProperty and unobserveProperty requests instead of subscribeEvent and unsubscribeEvent requests
Events (three stages)
- The subscription test
- The testbench sends the specified request for subscribeEvent to the Thing. Node-wot can in some cases not differentiate between an successful subscription and an unsuccessful subscription with the Thing just not emitting an event, so the subscription test has essentially three different outcomes: Successful, Timeout and Failed. Successful describes the case where node-wot confirms a successful subscription, Timeout describes the case where node-wot can not differentiate (see above) and Failed describes the case where node-wot throws an error during subscription (The timeout length can be configured in the testConfig).
- The listening phase
- Is only active if the subscription was successful.
- The testbench listens for any incoming data for this subscription. Any received data is verified against output specified in the TD.
- The listening phase ends after a configurable amount of time or when a configurable amount of data packages was received (both options can be configured in the testConfig).
- The cancel subscription test (depends on the outcome fot the subscription test)
- If subscription test was successful the testbench sends the specified request for unsubscribeEvent.
- If subscription test was a timeout, the testbench can not know if the subscription was successful so it does not test anything.
- If subscription test was a fail the testbench can obviously not cancel the subscription so it does not test anything.
- The subscription test
The test request is returned with the current state of the testReport.
- The testReport property is updated with the current state of the testReport
Synchronous listening Phase (only if events or observable properties are present; optional)
- Can be explicitly deactivated in the testConfig.
- All Events and observable properties are tested again but this time synchronously. This synchronous testing needs significantly less time but results in a pretty hard to read log.
- The timeout length, listening length and the received data package threshold can all be configured independent of the listening phase of the sequential tests in the testConfig.
Ending Phase
- The finished testReport is written to the storage
- If the Synchronous listening phase was present the testReport property is updated to the current state.
- The testbench is reset to be ready for the next test run.
TLDR
How does the coverage testing work?
How does the vulnerability testing work?
Starting Phase
- The security scheme and the string covering that scheme are determined.
- The usernames and passwords (found under
Resources/) are read from files. IffastModeis true (this is the case whentestVulnerabilitiesis called fromfastTest), then only a small number of username-password pairs are tested, as testing may take significant time intervals which is not the case wanted infastTest.
Main Phase
The thing is checked if it has properties and actions.
If it has properties, then:
Every property is checked if it is
writeOnlyor not.If
writeOnly:The request options are created for the
writepropertyoperation, then dictionary attack is performed.If dictionary attack finds suitable credentials OR credentials are given via config file, then safety tests are performed.
During safety testing, property is first checked for writing types that should and should not be optimally allowed, then checked if it is readable.
If not
writeonly:The request options are created for the
readpropertyoperation, then dictionary attack is performed.If dictionary attack finds suitable credentials OR credentials are given via config file, then safety tests are performed.
During safety testing, property is first checked if it is readable, then checked if it is writable. While performing writing tests, property is checked with types that should and should not be optimally allowed.
If it has actions, then for every action:
The request options are created for the
invokeactionoperation, then dictionary attack is performed.If dictionary attack finds suitable credentials OR credentials are given via config file, then safety tests are performed.
During safety testing, action are tested if they accept types that should not be optimally allowed.