Skip to content

Instantly share code, notes, and snippets.

@bowsersenior
Created August 18, 2012 17:04
Show Gist options
  • Save bowsersenior/3388412 to your computer and use it in GitHub Desktop.
Save bowsersenior/3388412 to your computer and use it in GitHub Desktop.
Gauntlt with turnip

Proof of concept of using Turnip as the engine for gauntlt

How to use this code

TL;DR

$ git clone git://gist.github.com/3388412.git spec
$ gem install turnip aruba
$ rspec -r ./spec/spec_helper.rb -r ./spec/steps/gauntlt_steps.rb spec/nmap_turnip.attack --format documentation

Step-by-step

  1. Create a new directory, I'll refer to it as 'test-gauntlt-project':

     $ mkdir test-gauntlt-project
     $ cd test-gauntlt-project
    
  2. Install turnip & aruba

     $ gem install turnip aruba
     # this also installs rspec & gherkin
    
  3. Clone this repo and name it 'spec'

     $ git clone git://gist.github.com/3388412.git spec
    
  4. Run rspec with:

     $ rspec -r ./spec/spec_helper.rb -r ./spec/steps/gauntlt_steps.rb spec/nmap_turnip.attack --format documentation
     # by passing -r, the support files are loaded, which tells rspec how to run the attack file
    
module Gauntlt
module Turnip
module Cli
def unescape(string)
string.gsub('\n', "\n").gsub('\"', '"').gsub('\e', "\e")
end
def detect_ruby(cmd)
if cmd =~ /^ruby\s/
cmd.gsub(/^ruby\s/, "#{current_ruby} ")
else
cmd
end
end
def current_dir
File.join(*dirs)
end
def dirs
@dirs ||= ['tmp/aruba']
end
def exit_timeout
3
end
def io_wait
0.1
end
def stop_process(process)
@last_exit_status = process.stop(announcer, @aruba_keep_ansi)
end
def stop_processes!
processes.each do |_, process|
stop_process(process)
end
end
def last_exit_status
return @last_exit_status if @last_exit_status
stop_processes!
@last_exit_status
end
def run_simple(cmd, fail_on_error=true)
run(cmd) do |process|
stop_process(process)
end
@timed_out = last_exit_status.nil?
assert_exit_status(0) if fail_on_error
end
def _mkdir(dir_name)
FileUtils.mkdir_p(dir_name) unless File.directory?(dir_name)
end
def in_current_dir(&block)
_mkdir(current_dir)
Dir.chdir(current_dir, &block)
end
class Announcer
def initialize(session, options = {})
@session, @options = session, options
end
def stdout(content)
return unless @options[:stdout]
print content
end
def stderr(content)
return unless @options[:stderr]
print content
end
def dir(dir)
return unless @options[:dir]
print "$ cd #{dir}"
end
def cmd(cmd)
return unless @options[:cmd]
print "$ #{cmd}"
end
def env(key, value)
return unless @options[:env]
print %{$ export #{key}="#{value}"}
end
private
def print(message)
@session.announce_or_puts(message)
end
end
require 'childprocess'
require 'tempfile'
require 'shellwords'
class Process
include Shellwords
def initialize(cmd, exit_timeout, io_wait)
@exit_timeout = exit_timeout
@io_wait = io_wait
@out = Tempfile.new("aruba-out")
@err = Tempfile.new("aruba-err")
@process = ChildProcess.build(*shellwords(cmd))
@process.io.stdout = @out
@process.io.stderr = @err
@process.duplex = true
end
def run!(&block)
@process.start
yield self if block_given?
end
def stdin
wait_for_io do
@process.io.stdin.sync = true
@process.io.stdin
end
end
def output(keep_ansi)
stdout(keep_ansi) + stderr(keep_ansi)
end
def stdout(keep_ansi)
wait_for_io do
@out.rewind
filter_ansi(@out.read, keep_ansi)
end
end
def stderr(keep_ansi)
wait_for_io do
@err.rewind
filter_ansi(@err.read, keep_ansi)
end
end
def stop(reader, keep_ansi)
return unless @process
unless @process.exited?
reader.stdout stdout(keep_ansi)
reader.stderr stderr(keep_ansi)
@process.poll_for_exit(@exit_timeout)
end
@process.exit_code
end
def terminate(keep_ansi)
if @process
stdout(keep_ansi) && stderr(keep_ansi) # flush output
@process.stop
stdout(keep_ansi) && stderr(keep_ansi) # flush output
end
end
private
def wait_for_io(&block)
sleep @io_wait if @process.alive?
yield
end
def filter_ansi(string, keep_ansi)
keep_ansi ? string : string.gsub(/\e\[\d+(?>(;\d+)*)m/, '')
end
end
def announcer
Announcer.new(self,
:stdout => @announce_stdout,
:stderr => @announce_stderr,
:dir => @announce_dir,
:cmd => @announce_cmd,
:env => @announce_env)
end
def processes
@processes ||= []
end
def register_process(name, process)
processes << [name, process]
end
def run(cmd)
@commands ||= []
@commands << cmd
cmd = detect_ruby(cmd)
in_current_dir do
#Aruba.config.hooks.execute(:before_cmd, self, cmd)
announcer.dir(Dir.pwd)
announcer.cmd(cmd)
process = Process.new(cmd, exit_timeout, io_wait)
register_process(cmd, process)
process.run!
block_given? ? yield(process) : process
end
end
def assert_passing_with(expected)
assert_exit_status_and_partial_output(true, expected)
end
def assert_exit_status_and_partial_output(expect_to_pass, expected)
assert_success(expect_to_pass)
assert_partial_output(expected, all_output)
end
def assert_success(success)
success ? assert_exit_status(0) : assert_not_exit_status(0)
end
def assert_exit_status(status)
last_exit_status.should eq(status),
append_output_to("Exit status was #{last_exit_status} but expected it to be #{status}.")
end
def assert_partial_output(expected, actual)
unescape(actual).should include(unescape(expected))
end
def append_output_to(message)
"#{message} Output:\n\n#{all_output}\n"
end
def all_stdout
stop_processes!
only_processes.inject("") { |out, ps| out << ps.stdout(@aruba_keep_ansi) }
end
def all_stderr
stop_processes!
only_processes.inject("") { |out, ps| out << ps.stderr(@aruba_keep_ansi) }
end
def all_output
all_stdout << all_stderr
end
def only_processes
processes.collect{ |_, process| process }
end
end
end
end
module TurnipCliSteps
include Gauntlt::Turnip::Cli
extend ::Turnip::DSL
step "the file :filename should contain: :partial_content" do |filename, partial_content|
check_file_content(file, partial_content, true)
end
step 'I run :cmd' do |cmd|
run_simple(unescape(cmd), false)
end
placeholder :cmd do
match /`([^`]*)`/ do |cmd|
cmd
end
end
step "I successfully run `:cmd`" do |cmd|
run_simple(unescape(cmd))
end
step "it should pass with:" do |partial_output|
assert_passing_with(partial_output)
end
end
module Gauntlt::Turnip::NmapSteps
extend ::Turnip::DSL
step '"nmap" is installed' do
if !(`which nmap` && $?.success?)
raise "nmap not installed!"
end
end
step 'the target hostname is "google.com"' do
end
step 'I launch an "nmap" attack with:' do |cmd|
end
step 'the output should contain:' do |string|
end
end
RSpec.configure do |config|
config.include TurnipCliSteps
end
Feature: nmap attacks
Background:
Given I run `which git`
Then it should pass with:
"""
/usr/bin/git
"""
Given "nmap" is installed
And the target hostname is "google.com"
Scenario: Verify server is available on standard web ports
When I launch an "nmap" attack with:
"""
nmap -p 80,443 <hostname>
"""
Then the output should contain:
"""
80/tcp open http
443/tcp open https
"""
@slow
Scenario: Detect OS
When I launch an "nmap" attack with:
"""
nmap -sV -p80 -PN <hostname>
"""
Then the output should contain:
"""
Service Info: OS: Linux; CPE: cpe:/o:linux:kernel
"""
Feature: nmap attacks
Background:
Given "nmap" is installed
And the target hostname is "google.com"
Scenario: Verify server is available on standard web ports
When I launch an "nmap" attack with:
"""
nmap -p 80,443 <hostname>
"""
Then the output should contain:
"""
80/tcp open http
443/tcp open https
"""
@slow
Scenario: Detect OS
When I launch an "nmap" attack with:
"""
nmap -sV -p80 -PN <hostname>
"""
Then the output should contain:
"""
Service Info: OS: Linux; CPE: cpe:/o:linux:kernel
"""
require 'rspec'
require 'turnip'
# Turnip only works with files that end in .feature
# So we create another duck-punck to RSpec's loader to handle .attack files
module Gauntlt
module Turnip
module RSpec
module Loader
def load(*a, &b)
if a.first.end_with?('.attack')
begin; require 'turnip_helper'; rescue LoadError; end
begin; require 'spec_helper'; rescue LoadError; end
::Turnip::RSpec.run(a.first)
else
super
end
end
end
end
end
end
# include the duck-puncher in RSpec
::RSpec::Core::Configuration.send(:include, Gauntlt::Turnip::RSpec::Loader)
# This is for customizing Gerkin with a new set of keywords
::RSpec.configure do |config|
config.pattern.gsub! "**/*.feature", ",**/*.attack"
end
# load step definitions
Dir.glob("steps/**/*steps.rb") { |f| load f, true }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment