Feb 17 2009

dradis extensions: how they work and how to write them

Category: Ruby, dradissiebert @ 1:33 am

Dradis is a tool used for structured information storage and sharing. Although it is applicable to various environments, it is originally aimed at information security consultants working alone or in a team. One of the great features of the application is that the client side’s functionality can be extended by what is called (quite creatively) – extensions.

In this post I’ll be looking at what an extension is, how it fits into the dradis framework and how to write your own extensions.

You are welcome to jump ahead to the How do I write my own extension? if that is the only part that you are interested in.

What is an extension?

In a nutshell – A dradis extension is an addition to the collection of functions offered by the dradis client side. The possibilities of what an extension can be are almost endless. From a logical perspective it will be a function that enhances the usability of the application within the context of the user.

Currently the functionality that is offered by an extension is triggered from the command line console on the client side. To see what are all the commands offered by your currently installed extensions type help in the command line console of the dradis client. You might see something similar to:

List of registered commands
---------------------------
help:	shows the help of all the commands available
reload:	reload the modules from the filesystem
quit:	exits the application
add:	add elements to the project
fact:	shows a random Chuck Norris fact
encode:	Encodes a string using various differnet encoding schemes
lookup:	Basically greps a file, useful for lookup tables
decode:	Decodes a string into the specified encoding type
export:	export the knowledge base to a local file
ls:	lists entries under the directory specified as argument
import:	import external resources to dradis

Some of those commands are hard coded into the console functionality but the majority are commands offered by your installed extensions. For example the fact command is offered to you by the chucknorris extension and the ls command is offered by the filesystem extension.

Extensions can also communicate to other parts of the application. In the majority of cases this will be communication to the data storage. For example the add command allows you to add data to the data storage and the export command allows you to export the data storage to a local file.

Where do extensions fit into the dradis framework?

Extension fact list:

  • As mentioned previously, extensions are found on the client side of the dradis framework.
  • When looking at the code they can be found in the client/extensions folder.
  • An extension provides one or more commands that can be called from the client command line console.
  • Executing one of these commands in the console triggers a call to a method in the extension code (Executing fact in the console triggers the fact method in the chucknorris.rb file).
  • The return value of the extension method is printed to the console interface (The return value of the fact method is a Chuck Norris fact that is printed to the console interface).
  • The extension communicates to the rest of the application through calls to service providers (I’ll define a service provider shortly).
  • An extension can be one of two types: simple or namespace (Definition of these will follow).


What is a service provider?
A service provider is an interface to a certain part of the application. It can be seen as a functionality presented through an API.
Lets look at an example: The dradis client side is modeled on a Model-View-Controller architecture. The extension code has access to an instance of the Controller (@controller). The extension does not have direct access to the Model (the Model is the access layer to the data storage). Calls to the Model is made through the data-storage service provider (client/core/service_providers/data_storage.rb. The data-storage service provider has a set of methods (an API) through which you can:

  • add a node – @controller.request_service(:model_add_node, …)
  • add a category – @controller.request_service(:model_add_category, …)
  • get the contents of a node – @controller.request_service(:model_find_node, …)
  • etc.

Have a look in the client/core/service_providers folder for all the currently available service providers.

What is a simple extension and a namespace extension?
A simple extension provides one or more commands that are grouped only by them being members of the extension. A command of a simple extension can be the only command in the current dradis client instance to have the name of that command. It is triggered in the command line console by: command parameters. The chucknorris extension is a simple extension and offers the fact command. No other simple extension can have a command called fact.

A namespace extension places its commands in a namespace. One namespace may contain commands with the same name as simple commands or commands in other namespaces. If we want to add a fact command that gives random Bruce Schneier facts then we can create the bruceschneier namespace and place a fact command in it that will not interfere with our Chuck Norris fact command. Namespace commands are called from the commandline in the format: namespace command parameter. The export extension is an example of a namespace extension.


How do I write my own extension?

Let’s have a look at the basic structure of a simple extension. Following is a simplified version of the chucknorris extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Extensions
  class Chucknorris < Extensions::Simple
    #----------------------------------------------------- Dispatcher implementation
    INFO = {
      :commands => {
        'fact' => {
          :desc => 'shows a random Chuck Norris fact',
          :syntax => [ ]
        }
      }
    }
 
    #----------------------------------------------------- commands implementation
    def fact(*args)
      # here we implement the magic that gets the new fact and returns it
    end
  end
end
  • line 1 – To implement our extension we need to extend the Extensions module
  • line 2 – We define our class to inherit from the Extensions::Simple class
  • lines 4-11 – The INFO hash defines all the commands in our extension for the use of the command launcher. In the case of the example above it defines the presence of the fact command with its description and its syntax. In this case there is no syntax defined as the command does not take any parameters.
  • lines 14-16 – This is the method implementation of the command that was defined in the INFO hash.

Now lets look at a slightly more complex namespace extension. The example is the add extension that hosts the node and category commands. The syntax for the two command are respectively:

add node  <parent id> <label>

and

add category <name>

This is the code for the extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
module Extensions
  class Add < Extensions::Namespace
    #----------------------------------------------------- private methods
    private
    #----------------------------------------------------- Dispatcher implementation
    INFO = {
      :namespace => 'add',
      :description => 'add elements to the project',
      :commands => {
        'node' => {
          :desc => 'Adds a node to the node tree.',
          :syntax => [
 
          {
            :required => true,
            :label => 'parent_id',
            :desc => 'The id of parent to the new node. Use 0 if it is to be a root node.',
            :regexp => /^\d*$/
          },
          {
            :required => true,
            :label => 'label',
            :desc => 'The label/name of the new node.',
            :regexp => /^\w[\w\s]*$/
          }
          ]
        },
        'category' => {
          :desc => 'Adds a note category.',
          :syntax => [
          {
            :required => true,
            :label => 'name',
            :desc => 'name of the new category',
            :regexp => /^\w[\w\s]*$/
          }
          ]
        }
      }
    }
 
    # Implements the add node fucntionality. See the above for allowed parameters
    def node(*args)
      # the parent_id needs to be an integer or a nil value
      args[1] = args[1] == '0' ? nil : args[1].to_i
      # call the service provider to add a node with the specified parameters
      @controller.request_service(:model_add_node, args[1], nil, args[2])
      return "Added node: #{args[2]}"
    end
 
    # Implements the add category fucntionality. See the above for allowed parameters
    def category(*args)
      # call the service provider to add a node with the specified parameters
      @controller.request_service(:model_add_category, args[1])
      return "Added category: #{args[1]}"
    end
  end
end

I’ll highlight the important and new bits of code:

  • line 2 – Different that in the previous example we define this extension to inherit from the Extensions::Namespace class
  • line 7 – In the INFO hash we define the namespace add. The contents associated with the add hash key defines the commands in the namespace.
  • lines 9-38 – Here we define our commads in a very similar fashion as in the previous example. We have added the complexity of each command accepting some parameters. This is defined by the array assigned to the syntax key
  • line 14 – States that the parameter is compulsary
  • line 15 – This is the displayed name of the parameter
  • line 16 – Defines the description of the command that will be printed in the help display of the command
  • line 17 – Specifies the regular expression by which the parameter will be validated by the command launcher
  • lines 42-55 – This is the implementation of the commands as defined in the INFO hash
  • line 44 – The args array contains the parameters that was passed. args[0] is the command, node in this case. args[1] contains the parent_id and args[2] contains the label
  • line 46 – This is an example of a call to a service provider in this case it calls a method in the data-storage service provider. (See the architecture section for a full discussion on this)
  • line 47 – The return value will be displayed in the command line console.

As a last bit of advice:

  • Name your extension file, class and if applicable namespace the same name
  • If you add something new to the data storage you might find that you can not reference the newly added record immediately. The reason is that you have to wait for the local cache of the data storage to be refreshed. You can force a refresh with a @controller.request_service(:model_knowledge_refresh) call.

That should leave you with a good understanding of dradis extensions.

As always – Happy dradis coding!

Popularity: 11% [?]

Share and Enjoy:
  • Digg
  • del.icio.us
  • Slashdot
  • Technorati

Leave a Reply