def start() -> None:
    """
    Set up a new proxy object with an error handler, configuration that we read
    from  argv[1], and the original user request from STDIN.
    """
    try:
        configure_logging()

        logging.debug("Starting SecureDrop Proxy {}".format(version))

        # path to config file must be at argv[1]
        if len(sys.argv) != 2:
            raise ValueError(
                "sd-proxy script not called with path to configuration file")

        # read config. `read_conf` will call `p.err_on_done` if there is a config
        # problem, and will return a Conf object on success.
        conf_path = sys.argv[1]
        # a fresh, new proxy object
        p = proxy.Proxy(conf_path=conf_path)

        # read user request from STDIN
        incoming_lines = []
        for line in sys.stdin:
            incoming_lines.append(line)
        incoming = "\n".join(incoming_lines)

        main.__main__(incoming, p)
    except Exception as e:
        response = {
            "status": http.HTTPStatus.INTERNAL_SERVER_ERROR,
            "body": json.dumps({"error": str(e)}),
        }
        print(json.dumps(response))
        sys.exit(1)
Esempio n. 2
0
    def test_custom_callbacks(self):
        """
        Test the handlers in a real proxy request.
        """
        conf = proxy.Conf()
        conf.host = "jsonplaceholder.typicode.com"
        conf.scheme = "https"
        conf.port = 443

        req = proxy.Req()
        req.method = "GET"

        on_save_addition = "added by the on_save callback\n"
        on_done_addition = "added by the on_done callback\n"

        def on_save(self, fh, res):
            res.headers["Content-Type"] = "text/plain"
            res.body = on_save_addition

        def on_done(self):
            self.res.headers["Content-Type"] = "text/plain"
            self.res.body += on_done_addition

        p = proxy.Proxy(self.conf_path, req)
        # Patching for tests
        p.conf = conf
        p.on_done = types.MethodType(on_done, p)
        p.on_save = types.MethodType(on_save, p)
        p.proxy()

        self.assertEqual(p.res.body, "{}{}".format(on_save_addition, on_done_addition))
Esempio n. 3
0
    def test_json_response(self):
        test_input_json = """{ "method": "GET",
                            "path_query": "/posts?userId=1" }"""

        req = proxy.Req()
        req.method = 'GET'
        req.path_query = ''
        req.headers = {'Accept': 'application/json'}

        # Use custom callbacks
        def on_save(res, fh, conf):
            pass

        def on_done(res):
            res = res.__dict__
            self.assertEqual(res['status'], 200)

        self.p = proxy.Proxy(self.conf, req, on_save)
        self.p.on_done = on_done
        self.p.proxy()

        saved_stdout = sys.stdout
        try:
            out = StringIO()
            sys.stdout = out
            main.__main__(test_input_json, self.p)
            output = out.getvalue().strip()
        finally:
            sys.stdout = saved_stdout

        response = json.loads(output)
        for item in json.loads(response['body']):
            self.assertEqual(item['userId'], 1)
Esempio n. 4
0
    def test_config_has_valid_keys(self):
        p = proxy.Proxy("tests/files/valid-config.yaml")

        # Verify we have a valid Conf object
        self.assertEqual(p.conf.host, "jsonplaceholder.typicode.com")
        self.assertEqual(p.conf.port, 443)
        self.assertFalse(p.conf.dev)
        self.assertEqual(p.conf.scheme, "https")
        self.assertEqual(p.conf.target_vm, "compost")
Esempio n. 5
0
    def test_on_save_200_success(self):
        fh = tempfile.NamedTemporaryFile()

        p = proxy.Proxy(self.conf_path)
        p.on_save(fh, self.res)

        self.assertEqual(self.res.headers["Content-Type"], "application/json")
        self.assertEqual(self.res.headers["X-Origin-Content-Type"], "application/json")
        self.assertEqual(self.res.status, 200)
        self.assertIn("filename", self.res.body)
Esempio n. 6
0
    def test_version(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = ''
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy()
        p.proxy()

        self.assertEqual(p.res.version, version.version)
Esempio n. 7
0
    def test_version(self):
        req = proxy.Req()
        req.method = "GET"
        req.path_query = ""
        req.headers = {"Accept": "application/json"}

        p = proxy.Proxy(self.conf_path)
        p.proxy()

        self.assertEqual(p.res.version, version.version)
Esempio n. 8
0
    def test_400_if_callback_not_set(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = ''
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy()
        p.proxy()

        self.assertEqual(p.res.status, 400)
Esempio n. 9
0
    def test_config_file_when_yaml_is_invalid(self):
        def err_on_done(self):
            res = self.res.__dict__
            assert res["status"] == 500
            assert "YAML syntax error" in res["body"]
            assert res["headers"]["Content-Type"] == "application/json"
            sys.exit(1)

        p = proxy.Proxy(self.conf_path)
        p.err_on_done = types.MethodType(err_on_done, p)
        with self.assertRaises(SystemExit):
            p.read_conf("tests/files/invalid_yaml.yaml")
Esempio n. 10
0
    def test_internal_server_error(self):
        """
        Test handling of "500 Internal Server Error" from the server.
        """
        req = self.make_request(path_query="/crash")
        p = proxy.Proxy(self.conf_path, req)
        p.proxy()

        self.assertEqual(p.res.status, http.HTTPStatus.INTERNAL_SERVER_ERROR)
        self.assertIn("application/json", p.res.headers["Content-Type"])
        body = json.loads(p.res.body)
        self.assertEqual(body["error"], http.HTTPStatus.INTERNAL_SERVER_ERROR.phrase.lower())
Esempio n. 11
0
    def test_bad_request(self):
        """
        Test handling of "400 Bad Request" from the server.
        """
        req = self.make_request(path_query="/bad")
        p = proxy.Proxy(self.conf_path, req)
        p.proxy()

        self.assertEqual(p.res.status, http.HTTPStatus.BAD_REQUEST)
        self.assertIn("application/json", p.res.headers["Content-Type"])
        body = json.loads(p.res.body)
        self.assertEqual(body["error"], http.HTTPStatus.BAD_REQUEST.phrase.lower())
Esempio n. 12
0
    def test_proxy_basic_functionality(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = ''
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy(self.conf, req, self.on_save)
        p.proxy()

        self.assertEqual(p.res.status, 200)
        self.assertEqual(p.res.body, json.dumps({'filename': self.fn}))
        self.assertEqual(p.res.headers['Content-Type'], 'application/json')
Esempio n. 13
0
    def test_config_file_does_not_exist(self):
        def err_on_done(self):
            res = self.res.__dict__
            assert res["status"] == 500
            assert "Configuration file does not exist" in res["body"]
            assert res["headers"]["Content-Type"] == "application/json"
            sys.exit(1)

        p = proxy.Proxy(self.conf_path)
        p.err_on_done = types.MethodType(err_on_done, p)
        with self.assertRaises(SystemExit):
            p.read_conf("not/a/real/path")
Esempio n. 14
0
    def test_proxy_400_no_handler(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = 'http://badpath.lol/path'
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy(self.conf, req)
        p.proxy()

        self.assertEqual(p.res.status, 400)
        self.assertEqual(p.res.headers['Content-Type'], 'application/json')
        self.assertIn('Request callback is not set', p.res.body)
Esempio n. 15
0
    def test_proxy_produces_404(self):
        req = proxy.Req()
        req.method = "GET"
        req.path_query = "/notfound"
        req.headers = {"Accept": "application/json"}

        p = proxy.Proxy(self.conf_path, req)

        p.proxy()

        self.assertEqual(p.res.status, 404)
        self.assertEqual(p.res.headers["Content-Type"], "application/json")
Esempio n. 16
0
    def test_config_500_when_missing_target_vm(self):
        def err_on_done(self):
            res = self.res.__dict__
            assert res["status"] == 500
            assert "missing `target_vm` key" in res["body"]
            assert res["headers"]["Content-Type"] == "application/json"
            sys.exit(1)

        p = proxy.Proxy(self.conf_path)
        p.err_on_done = types.MethodType(err_on_done, p)

        with self.assertRaises(SystemExit):
            p.read_conf("tests/files/missing-target-vm.yaml")
Esempio n. 17
0
    def test_proxy_200_valid_path(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = '/posts/1'
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy(self.conf, req, self.on_save)
        p.proxy()

        self.assertEqual(p.res.status, 200)
        self.assertIn('application/json', p.res.headers['Content-Type'])
        body = json.loads(p.res.body)
        self.assertEqual(body['userId'], 1)
Esempio n. 18
0
    def test_proxy_500_misconfiguration(self):
        req = proxy.Req()
        req.method = "GET"
        req.path_query = "/posts/1"
        req.headers = {"Accept": "application/json"}

        p = proxy.Proxy(self.conf_path, req)

        p.proxy()

        self.assertEqual(p.res.status, 500)
        self.assertEqual(p.res.headers["Content-Type"], "application/json")
        self.assertIn("Proxy error while generating URL to request", p.res.body)
Esempio n. 19
0
    def test_cannot_connect(self):
        """
        Test for "502 Bad Gateway" when the server can't be reached.
        """
        req = self.make_request()

        p = proxy.Proxy("tests/files/badgateway-config.yaml", req)
        p.proxy()

        self.assertEqual(p.res.status, http.HTTPStatus.BAD_GATEWAY)
        self.assertIn("application/json", p.res.headers["Content-Type"])
        body = json.loads(p.res.body)
        self.assertEqual(body["error"], "could not connect to server")
Esempio n. 20
0
    def test_proxy_400_bad_path(self):
        req = proxy.Req()
        req.method = "GET"
        req.path_query = "http://badpath.lol/path"
        req.headers = {"Accept": "application/json"}

        p = proxy.Proxy(self.conf_path, req)

        p.proxy()

        self.assertEqual(p.res.status, 400)
        self.assertEqual(p.res.headers["Content-Type"], "application/json")
        self.assertIn("Path provided in request did not look valid", p.res.body)
Esempio n. 21
0
    def test_on_save_500_unhandled_error(self):
        fh = tempfile.NamedTemporaryFile()

        # Let's generate an error and ensure that an appropriate response
        # is sent back to the user
        with patch("subprocess.run", side_effect=IOError):
            p = proxy.Proxy(self.conf_path)
            p.on_save(fh, self.res)

        self.assertEqual(self.res.status, 500)
        self.assertEqual(self.res.headers["Content-Type"], "application/json")
        self.assertEqual(self.res.headers["X-Origin-Content-Type"], "application/json")
        self.assertIn("Unhandled error", self.res.body)
Esempio n. 22
0
    def test_default_callbacks(self):
        test_input = {
            "method": "GET",
            "path_query": "",
        }

        p = proxy.Proxy(self.conf_path, proxy.Req())
        p.on_done = unittest.mock.MagicMock()
        p.on_save = unittest.mock.MagicMock()

        main.__main__(json.dumps(test_input), p)
        self.assertEqual(p.on_save.call_count, 1)
        self.assertEqual(p.on_done.call_count, 1)
Esempio n. 23
0
    def test_proxy_500_misconfiguration(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = '/posts/1'
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy(self.conf, req, self.on_save)
        p.proxy()

        self.assertEqual(p.res.status, 500)
        self.assertEqual(p.res.headers['Content-Type'], 'application/json')
        self.assertIn('Proxy error while generating URL to request',
                      p.res.body)
Esempio n. 24
0
    def test_input_headers(self):
        test_input = {
            "method": "GET",
            "path_query": "/posts?userId=1",
            "headers": {"X-Test-Header": "th"},
        }

        def on_save(self, fh, res):
            pass

        p = proxy.Proxy(self.conf_path, proxy.Req())
        main.__main__(json.dumps(test_input), p)
        self.assertEqual(p.req.headers, test_input["headers"])
Esempio n. 25
0
    def test_proxy_produces_404(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = '/notfound'
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy(self.conf, req)
        p.on_save = self.on_save
        p.on_done = self.on_done
        p.proxy()

        self.assertEqual(p.res.status, 404)
        self.assertEqual(p.res.headers['Content-Type'], 'application/json')
Esempio n. 26
0
    def test_proxy_handles_query_params_gracefully(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = '/posts?userId=1'
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy(self.conf, req, self.on_save)
        p.proxy()

        self.assertEqual(p.res.status, 200)
        self.assertIn('application/json', p.res.headers['Content-Type'])
        body = json.loads(p.res.body)
        for item in body:
            self.assertEqual(item['userId'], 1)
Esempio n. 27
0
    def test_proxy_200_valid_path(self):
        req = proxy.Req()
        req.method = "GET"
        req.path_query = "/posts/1"
        req.headers = {"Accept": "application/json"}

        p = proxy.Proxy(self.conf_path, req)

        p.proxy()

        self.assertEqual(p.res.status, 200)
        self.assertIn("application/json", p.res.headers["Content-Type"])
        body = json.loads(p.res.body)
        self.assertEqual(body["userId"], 1)
Esempio n. 28
0
    def test_proxy_400_bad_path(self):
        req = proxy.Req()
        req.method = 'GET'
        req.path_query = 'http://badpath.lol/path'
        req.headers = {'Accept': 'application/json'}

        p = proxy.Proxy(self.conf, req)
        p.on_save = self.on_save
        p.on_done = self.on_done
        p.proxy()

        self.assertEqual(p.res.status, 400)
        self.assertEqual(p.res.headers['Content-Type'], 'application/json')
        self.assertIn('Path provided in request did not look valid',
                      p.res.body)
Esempio n. 29
0
    def test_config_file_open_generic_exception(self):
        def err_on_done(self):
            res = self.res.__dict__
            assert res["status"] == 500
            assert res["headers"]["Content-Type"] == "application/json"
            sys.exit(1)

        p = proxy.Proxy(self.conf_path)
        p.err_on_done = types.MethodType(err_on_done, p)

        with self.assertRaises(SystemExit):
            # Patching open so that we can simulate a non-YAML error
            # (e.g. permissions)
            with patch("builtins.open", side_effect=IOError):
                p.read_conf("tests/files/valid-config.yaml")
Esempio n. 30
0
    def test_proxy_handles_query_params_gracefully(self):
        req = proxy.Req()
        req.method = "GET"
        req.path_query = "/posts?userId=1"
        req.headers = {"Accept": "application/json"}

        p = proxy.Proxy(self.conf_path, req)

        p.proxy()

        self.assertEqual(p.res.status, 200)
        self.assertIn("application/json", p.res.headers["Content-Type"])
        body = json.loads(p.res.body)
        for item in body:
            self.assertEqual(item["userId"], 1)