]> git-server-git.apps.pok.os.sepia.ceph.com Git - s3-tests.git/commitdiff
S3 Fuzzer: Change direction towards decision tree
authorKyle Marsh <kyle.marsh@dreamhost.com>
Mon, 8 Aug 2011 23:51:10 +0000 (16:51 -0700)
committerKyle Marsh <kyle.marsh@dreamhost.com>
Mon, 12 Sep 2011 19:51:01 +0000 (12:51 -0700)
Fuzzer now builds requests based on a DAG that describes the request space
and attack surface.

request_decision_graph.yml [new file with mode: 0644]
s3tests/functional/test_fuzzer.py [new file with mode: 0644]
s3tests/fuzz_headers.py

diff --git a/request_decision_graph.yml b/request_decision_graph.yml
new file mode 100644 (file)
index 0000000..774d391
--- /dev/null
@@ -0,0 +1,36 @@
+start:
+    set: null
+    choice:
+        - bucket
+
+bucket:
+    set:
+        urlpath: '/{bucket}'
+    choice:
+        - bucket_get
+        - bucket_put
+        - bucket_delete
+
+bucket_delete:
+    set:
+        method: 'DELETE'
+    choice:
+        - delete_bucket
+        - delete_bucket_policy
+        - delete_bucket_website
+
+delete_bucket:
+    set:
+        query: null
+    choice: null
+
+delete_bucket_policy:
+    set:
+        query: 'policy'
+    choice: null
+
+delete_bucket_website:
+    set:
+        query: 'website'
+    choice: null
+
diff --git a/s3tests/functional/test_fuzzer.py b/s3tests/functional/test_fuzzer.py
new file mode 100644 (file)
index 0000000..0b28653
--- /dev/null
@@ -0,0 +1,35 @@
+import nose
+import random
+import string
+import yaml
+
+from s3tests.fuzz_headers import *
+
+from nose.tools import eq_ as eq
+from nose.plugins.attrib import attr
+
+from .utils import assert_raises
+
+_decision_graph = {}
+
+def check_access_denied(fn, *args, **kwargs):
+    e = assert_raises(boto.exception.S3ResponseError, fn, *args, **kwargs)
+    eq(e.status, 403)
+    eq(e.reason, 'Forbidden')
+    eq(e.error_code, 'AccessDenied')
+
+
+def read_graph():
+    graph_file = open('request_decision_graph.yml', 'r')
+    return yaml.safe_load(graph_file)
+
+
+def test_assemble_decision():
+    graph = read_graph()
+    prng = random.Random(1)
+    decision = assemble_decision(graph, prng)
+    decision['path']
+    decision['method']
+    decision['body']
+    decision['headers']
+
index dc63a4424a260c157534e8f6aaa6a54d131ee248..9886b25d5bb09ab5f2795349ac7fed8d59a3932e 100644 (file)
@@ -6,71 +6,23 @@ from . import common
 import traceback
 import random
 import string
+import yaml
 import sys
 
 
-class FuzzyRequest(object):
-    """ FuzzyRequests are initialized with a random seed and generate data to
-        get sent as valid or valid-esque HTTP requests for targeted fuzz testing
+def assemble_decision(decision_graph, prng):
+    """ Take in a graph describing the possible decision space and a random
+        number generator and traverse the graph to build a decision
     """
-    def __init__(self, seed):
-        self.random = random.Random()
-        self.seed = seed
-        self.random.seed(self.seed)
+    raise NotImplementedError
 
-        self._generate_method()
-        self._generate_path()
-        self._generate_body()
-        self._generate_headers()
 
-
-    def __str__(self):
-        s = '%s %s HTTP/1.1\n' % (self.method, self.path)
-        for header, value in self.headers.iteritems():
-            s += '%s: ' %header
-            if isinstance(value, list):
-                for val in value:
-                    s += '%s ' %val
-            else:
-                s += value
-            s += '\n'
-        s += '\n' # Blank line after headers are done.
-        s += '%s\r\n\r\n' %self.body
-        return s
-
-
-    def _generate_method(self):
-        METHODS = ['GET', 'POST', 'HEAD', 'PUT']
-        self.method = self.random.choice(METHODS)
-
-
-    def _generate_path(self):
-        path_charset = string.letters + string.digits
-        path_len = self.random.randint(0,100)
-        self.path = ''
-        for _ in xrange(path_len):
-            self.path += self.random.choice(path_charset)
-        self.auth_path = self.path # Not sure how important this is for these tests
-
-
-    def _generate_body(self):
-        body_charset = string.printable
-        body_len = self.random.randint(0, 1000)
-        self.body = ''
-        for _ in xrange(body_len):
-            self.body += self.random.choice(body_charset)
-
-
-    def _generate_headers(self):
-        self.headers = {'Foo': 'bar', 'baz': ['a', 'b', 'c']} #FIXME
-
-
-    def authorize(self, connection):
-        #Stolen shamelessly from boto's connection.py
-        connection._auth_handler.add_auth(self)
-        self.headers['User-Agent'] = UserAgent
-        if not self.headers.has_key('Content-Length'):
-            self.headers['Content-Length'] = str(len(self.body))
+def expand_decision(decision, prng):
+    """ Take in a decision and a random number generator.  Expand variables in
+        decision's values and headers until all values are fully expanded and
+        build a request out of the information
+    """
+    raise NotImplementedError
 
 
 def parse_options():
@@ -79,8 +31,10 @@ def parse_options():
     parser.add_option('--seed', dest='seed', type='int',  help='initial seed for the random number generator', metavar='SEED')
     parser.add_option('--seed-file', dest='seedfile', help='read seeds for specific requests from FILE', metavar='FILE')
     parser.add_option('-n', dest='num_requests', type='int',  help='issue NUM requests before stopping', metavar='NUM')
+    parser.add_option('--decision-graph', dest='graph_filename',  help='file in which to find the request decision graph', metavar='NUM')
 
     parser.set_defaults(num_requests=5)
+    parser.set_defaults(graph_filename='request_decision_graph.yml')
     return parser.parse_args()
 
 
@@ -107,16 +61,29 @@ def _main():
     else:
         request_seeds = randomlist(options.num_requests, options.seed)
 
+    graph_file = open(options.graph_filename, 'r')
+    decision_graph = yaml.safe_load(graph_file)
+
+    constants = {
+        'bucket_readable': 'TODO',
+        'bucket_writable' : 'TODO',
+        'bucket_nonexistant' : 'TODO',
+        'object_readable' : 'TODO',
+        'object_writable' : 'TODO',
+        'object_nonexistant' : 'TODO'
+    }
+
     for request_seed in request_seeds:
-        fuzzy = FuzzyRequest(request_seed)
-        fuzzy.authorize(s3_connection)
-        print fuzzy.seed, fuzzy
-        #http_connection = s3_connection.get_http_connection(s3_connection.host, s3_connection.is_secure)
-        #http_connection.request(fuzzy.method, fuzzy.path, body=fuzzy.body, headers=fuzzy.headers)
-
-        #response = http_connection.getresponse()
-        #if response.status == 500 or response.status == 503:
-            #print 'Request generated with seed %d failed:\n%s' % (fuzzy.seed, fuzzy)
+        prng = random.Random(request_seed)
+        decision = assemble_decision(decision_graph, prng)
+        decision.update(constants)
+        request = expand_decision(decision, prng) 
+
+        response = s3_connection.make_request(request['method'], request['path'], data=request['body'], headers=request['headers'], override_num_retries=0)
+
+        if response.status == 500 or response.status == 503:
+            print 'Request generated with seed %d failed:\n%s' % (request_seed, request)
+        pass
 
 
 def main():