Activity Runner is a framework to test telemetry apps. It reads a JSON list of activities and executes them. A log in JSON format tracks the executed activities for comparison with telemetry app behaviors.
Supported activities:
- Start a process, given a path to an executable file and the desired (optional) command-line arguments
- Create a file of a specified type at a specified location
- Modify a file
- Delete a file
- Establish a network connection and transmit data
Install Ruby if needed.
Ruby version: 3.3.5
Install gems:
bundle install
Docker build:
docker compose build
Using the provided activities.json as a template, create a list of activities. It is an array of JSON objects, each specifying an action, a path, and any additional arguments.
The valid actions are:
run_process, properties:action,path, (optional)argscreate_file, properties:action,pathmodify_file, properties:action,path,datadelete_file, properties:action,pathnetwork_request, properties:action,path,data, (optional)protocol
bundle exec rspec
The provided script takes the activities file as an argument. The script logs to the default logfile, activity_runner_log.json. Note that log timestamps are in UTC.
./run_activities.rb <your activities.json file>
Call ActivityRunner from irb or another Ruby program. Optionally specify an alternate logfile with the logfile named argument.
ActivityRunner.new(activity_file, logfile: './activity_runner_log.json')
docker-compose run --rm --service-ports app
Running arbitrary processes, file operations, and network requests is a big security risk. For a production version, the user that runs the new processes should have carefully limited permissions. Possibly, file operations should be limited to an application subdirectory instead of being full paths.
Currently, no API keys are in use. If they are needed they should be added in a .env file and the dotenv gem can be added to the project. Also add env_file: .env to docker-compose.yml
ActivityRunnerreads in a json file of activities, createsActivityobjects, and runs them.Activityis the parent class for all types of activities. It can run a process because most activities use that capability. Inheritance and the Factory pattern are used to make this framework easily extensible.CreateFileActivity,ModifyFileActivity,DeleteFileActivity, andNetworkActivityare subclasses ofActivity. They override thecommandmethod and possibly therunmethod.NullFileActivityis used when the specifiedactionproperty is invalid or 'path' is missing.
ActivityFactorycreates the correctActivitysubclass for the specified action.
- json_logger - logging in json format
- faraday - network requests
- vcr - record network requests for specs
- sys-proctable - cross-platform information about running processes
- get_process_start_time - returns process start time (Linux only). I ended up using
psinstead because it just works on both Mac and Linux, but it's slow. In a production system, the Linux side should use this gem, and the Mac side should figure out the start time fromProcTable.ps(pid: process_id).start_tvsecand.start_tvusec.
For a production system, more specs should be added to check error paths, such as invalid JSON files, missing properties, etc.
Specs pass on both Mac and Linux.
At first I used curl in a new process to implement a network request. However, I did not have access to
requested properties such as amount of data sent and source address and port. I reimplemented the network
requests with faraday (which also allowed me to add the vcr gem to prevent network connections from specs).
This puts the network request in the same process as the overall activity runner.
Regarding source address and port, I looked at X-Forwarded-For, Origin, and Referer, but those are request headers, not response headers. Per this StackOverflow answer it would be possible to implement custom middleware to get access to the request to log those headers if they are present.