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)args
create_file
, properties:action
,path
modify_file
, properties:action
,path
,data
delete_file
, properties:action
,path
network_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
ActivityRunner
reads in a json file of activities, createsActivity
objects, and runs them.Activity
is 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
, andNetworkActivity
are subclasses ofActivity
. They override thecommand
method and possibly therun
method.NullFileActivity
is used when the specifiedaction
property is invalid or 'path' is missing.
ActivityFactory
creates the correctActivity
subclass 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
ps
instead 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_tvsec
and.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.