Author Archives: Xavi Vila

That silly moment when a ruby gem doesn’t install

A few days ago I was helping a user to install a custom Dradis plugin.

As you may already know, Dradis plugins are ruby gems and we manage them with bundler.

The final step in the installation process usually looks something like this:

bundle install --without development,test

or

bundle update --without development,test [custom_gem]

But at some point in the installation history, a mix of both commands was run, probably something like:

bundle install --without development,test [custom_gem]

This command is wrong, bundle install does not expect a specific gem as a parameter. So the custom_gem parameter is handled by bundler as one of the groups of gems not to be installed. Bundler notifies us about that:

Gems in the groups developement,test and custom_gem were not installed.

We may notice that warning (or not), and try to execute the command correctly:

bundle install --without development,test

But we will see the same warning about custom_gem not being installed. This is because bundler uses a config file to cache some configuration options, like the –without option. That file probably in the same app folder under:

.bundler/config

If we check its contents, it looks like:

---
BUNDLE_WITHOUT: "developement,test:custom_gem"

Until we delete that file or edit it to remove the custom_gem from it, we may have a hard time installing our gem.

Attachments API using ruby

One of the latest additions in Dradis Pro release 2.6.0 was the attachments API. Until now that was only available using the web interface:

Web interface attachments widget, instead of attachments api new endpoint

Web interface attachments widget

As documented here that new API endpoint allows to manipulate node attachments via REST requests. Here there are a couple of examples, using curl.

Read attachments associated to a specific node:

curl \
 -H 'Authorization: Token token="iOEFCQDR-miTHNTjiBxObjWC"' \
 -H 'Dradis-Project-Id: 8' \
 http://dradis.ip/pro/api/nodes/18/attachments

The response to this request is a JSON list of attachments in that node:

[
  {
    "filename": "burp.xml",
    "link": "/nodes/18/attachments/burp.xml"
  },
  {
    "filename": "screenshot.png",
    "link": "/nodes/18/attachments/screenshot.png"
  }
]

This is a request to attach some other files to that node:

curl \
 -H 'Authorization: Token token="iOEFCQDR-miTHNTjiBxObjWC"' \
 -H 'Dradis-Project-Id: 8' \
 -X POST \
 -F 'files[]=@/your/local/path/image1.png' -F 'files[]=@/your/local/path/image2.png' \
 http://dradis.ip/pro/api/nodes/18/attachments

The response to this request is a JSON list containing the new attachments info:

[
  {
    "filename": "image1.png",
    "link": "/nodes/18/attachments/image1.png"
  },
  {
    "filename": "image2.png",
    "link": "/nodes/18/attachments/image2.png"
  }
]

In addition in this post we would like to extend that documentation providing examples on how to do that using a programming language. Since Dradis is implemented in ruby, here is how we could do that in ruby.

Using ruby there are many libraries that allow us to perform http requests, from the basic
already included ‘net/http‘ to more high level options like ‘rest_client‘, ‘faraday‘, etc…

We will show basic examples using these three mentioned options.
For each option we provide two examples:

  1. a request to get all attachments in a node
  2. a requests to upload a couple of files to a node (in the attachments endpoint many files can be uploaded with a single request).

If you intend to use the examples below, remember that you should use your virtual appliance IP instead of ‘dradis.ip‘. Also change the token, project id and node id in the examples to your own values.

Attachments API using ‘rest-client’ ruby gem:

First of all we will need to install the ‘rest-client’ ruby gem. It can be installed with:

gem install rest-client

Read attachments associated to a specific node:

require 'rest_client'
RestClient.get(
  'http://dradis.ip/pro/api/nodes/18/attachments',
  {
    'Authorization' => 'Token token="iOEFCQDR-miTHNTjiBxObjWC"',
    'Dradis-Project-Id' => '8'
  }
)

Attach some other files to that node:

require 'rest_client'
RestClient.post(
  'http://dradis.ip/pro/api/nodes/18/attachments',
  {
    'files' => [
      File.new("/your/local/path/image1.png", 'rb'),
      File.new("/your/local/path/image2.png", 'rb')
    ]
  },
  {
    'Authorization' => 'Token token="iOEFCQDR-miTHNTjiBxObjWC"',
    'Dradis-Project-Id' => '8'
  }
)

Attachments API using ‘faraday’ ruby gem:

To install faraday:

gem install faraday

In this case we are trying to reuse the same connection, probably useful when building a script that sends many requests to the same endpoint.

require 'faraday'

# Establish connection
conn = Faraday.new(
  url: 'http://dradis.ip/pro/api/nodes/18/attachments',
  headers: {
    'Authorization' => 'Token token="iOEFCQDR-miTHNTjiBxObjWC"',
    'Dradis-Project-Id' => '8'
  }
) do |faraday|
  faraday.request :multipart
  faraday.adapter :net_http
end

# Read attachments associated to a specific node:
get = conn.get
puts get.body

# Attach some other files to that node
post = conn.post(
  nil,
  {
    'files' => [
      Faraday::UploadIO.new("/your/local/path/image1.png", 'image/png'),
      Faraday::UploadIO.new("/your/local/path/image2.png", 'image/png')
    ]
  }
)
puts post.body

Attachments API using ruby ‘net/http’:

‘net/http’ is part of the ruby standard library, so if you already have ruby nothing else should be installed to run this script. As a counterpart this option works at a lower level than the previous ones, therefore the code looks a bit more complex.

require 'net/http'

uri = URI('http://dradis.ip/pro/api/nodes/18/attachments')

Net::HTTP.start(uri.host, uri.port) do |http|
 
  # Read attachments associated to a specific node:
  get_request = Net::HTTP::Get.new uri
  get_request['Authorization'] = 'Token token="iOEFCQDR-miTHNTjiBxObjWC"'
  get_request['Dradis-Project-Id'] = '8'
  get_response = http.request(get_request)
  puts get_response.body

  # Attach some other files to that node:
  BOUNDARY = "AaB03x"
  file1 = '/your/local/path/image1.png'
  file2 = '/your/local/path/image2.png'

  post_body = []

  post_body << "--#{BOUNDARY}\r\n"

  post_body << "Content-Disposition: form-data; name=\"files[]\"; filename=\"#{File.basename(file1)}\"\r\n"
  post_body << "Content-Type: image/png\r\n"
  post_body << "\r\n"
  post_body << File.read(file1)

  post_body << "\r\n--#{BOUNDARY}\r\n"

  post_body << "Content-Disposition: form-data; name=\"files[]\"; filename=\"#{File.basename(file2)}\"\r\n"
  post_body << "Content-Type: image/png\r\n"
  post_body << "\r\n"
  post_body << File.read(file2)

  post_body << "\r\n--#{BOUNDARY}--\r\n"

  post_request = Net::HTTP::Post.new uri
  post_request['Authorization'] = 'Token token="iOEFCQDR-miTHNTjiBxObjWC"'
  post_request['Dradis-Project-Id'] = '8'
  post_request.body = post_body.join
  post_request["Content-Type"] = "multipart/form-data, boundary=#{BOUNDARY}"

  post_response = http.request(post_request)
  puts post_response.body
end

Final thoughts

In conclusion, sending requests to the API should be easy enough from any programming language. In the ruby case, using a specialized gem seems like the best choice.