Esempio n. 1
0
    def _on_xpath(self, unused_env, url_config, unused_client_cert, data):
        """ Handles the authentication based on XPath expressions.
        """
        if not data:
            return AuthResult(False, AUTH_XPATH_NO_DATA)

        request = etree.fromstring(data)

        prefix = 'xpath-'
        expressions = [url_config[header] for header in url_config if header.startswith(prefix)]

        if not expressions:

            # It's clearly an error. We've been requested to use XPath yet no
            # expressions have been defined in the config.
            raise SecWallException('No XPath expressions were found in the config')

        for expr in expressions:
            if not expr(request):
                return AuthResult(False, AUTH_XPATH_EXPR_MISMATCH)
        else:
            auth_result = AuthResult(True, '0')
            auth_result.auth_info = map(str, expressions)

            return auth_result
Esempio n. 2
0
def on_ssl_cert(url_config, client_cert, field_prefix, needs_auth_info=True):
    """ Visit _RequestApp._on_ssl_cert method's docstring.
    """
    if client_cert:
        config_fields = {}
        for field, value in url_config.items():
            if field.startswith(field_prefix):
                config_fields[field.split(field_prefix)[1]] = value

        # There are no fields so the user just wants the connection be
        # encrypted and the client use client certificate however they're
        # not interested in the cert's fields - so as long as the CA is
        # OK (and we know it is because otherwise we wouldn't have gotten
        # so far), we let the client in.
        if not config_fields:
            return True
        else:
            subject =  client_cert.get('subject')
            if not subject:
                return AuthResult(False, AUTH_CERT_NO_SUBJECT)

            cert_fields = dict((elem[0][0].encode('utf-8'), elem[0][1].encode('utf-8')) for elem in subject)
            
            for config_field, config_value in config_fields.items():
                cert_value = cert_fields.get(config_field)
                if not cert_value:
                    return AuthResult(False, AUTH_CERT_NO_VALUE)
                if cert_value != config_value:
                    return AuthResult(False, AUTH_CERT_VALUE_MISMATCH)
            else:
                auth_result = AuthResult(True, '0')
                if needs_auth_info:
                    auth_result.auth_info = dict((quote_plus(k), quote_plus(v)) for k, v in cert_fields.iteritems())

                return auth_result
Esempio n. 3
0
    def _on_xpath(self, unused_env, url_config, unused_client_cert, data):
        """ Handles the authentication based on XPath expressions.
        """
        if not data:
            return AuthResult(False, AUTH_XPATH_NO_DATA)

        request = etree.fromstring(data)

        prefix = 'xpath-'
        expressions = [url_config[header] for header in url_config if header.startswith(prefix)]

        if not expressions:

            # It's clearly an error. We've been requested to use XPath yet no
            # expressions have been defined in the config.
            raise SecWallException('No XPath expressions were found in the config')

        for expr in expressions:
            if not expr(request):
                return AuthResult(False, AUTH_XPATH_EXPR_MISMATCH)
        else:
            auth_result = AuthResult(True, '0')
            auth_result.auth_info = map(str, expressions)

            return auth_result
Esempio n. 4
0
    def _on_custom_http(self, env, url_config, *ignored):
        """ Handles the authentication based on custom HTTP headers.
        """
        prefix = 'custom-http-'

        expected_headers = {}
        expected_headers_keys = (header for header in url_config if header.startswith(prefix))

        for key in expected_headers_keys:
            # This set of operations (.split, .upper, .replace) could be done once
            # when the config's read, well, it's a room for improvement.
            expected_headers[str(key)] = str(env.get('HTTP_' + key.split(prefix)[1].upper().replace('-', '_'), ''))

        if not expected_headers:

            # It's clearly an error. We've been requested to use custom HTTP
            # headers but none are in the config.
            raise SecWallException('No custom HTTP headers were found in the config')

        for key, value in expected_headers.iteritems():
            if not value:
                return AuthResult(False, AUTH_CUSTOM_HTTP_NO_HEADER)

            if value != url_config[key]:
                return AuthResult(False, AUTH_CUSTOM_HTTP_HEADER_MISMATCH)
        else:
            auth_result = AuthResult(True, '0')
            auth_result.auth_info = expected_headers

            return auth_result
Esempio n. 5
0
    def _on_custom_http(self, env, url_config, *ignored):
        """ Handles the authentication based on custom HTTP headers.
        """
        prefix = 'custom-http-'

        expected_headers = {}
        expected_headers_keys = (header for header in url_config if header.startswith(prefix))

        for key in expected_headers_keys:
            # This set of operations (.split, .upper, .replace) could be done once
            # when the config's read, well, it's a room for improvement.
            expected_headers[str(key)] = str(env.get('HTTP_' + key.split(prefix)[1].upper().replace('-', '_'), ''))

        if not expected_headers:

            # It's clearly an error. We've been requested to use custom HTTP
            # headers but none are in the config.
            raise SecWallException('No custom HTTP headers were found in the config')

        for key, value in expected_headers.iteritems():
            if not value:
                return AuthResult(False, AUTH_CUSTOM_HTTP_NO_HEADER)

            if value != url_config[key]:
                return AuthResult(False, AUTH_CUSTOM_HTTP_HEADER_MISMATCH)
        else:
            auth_result = AuthResult(True, '0')
            auth_result.auth_info = expected_headers

            return auth_result
Esempio n. 6
0
def test_auth_result_nonzero():
    """ Tests AuthResult in boolean contexts.
    """
    # It's False by default.
    a1 = AuthResult()
    eq_(False, bool(a1))

    a2 = AuthResult(True)
    eq_(True, bool(a2))
Esempio n. 7
0
def on_wsse_pwd(wsse, url_config, data, needs_auth_info=True):
    """ Visit _RequestApp._on_wsse_pwd method's docstring.
    """
    if not data:
        return AuthResult(False, AUTH_WSSE_NO_DATA)

    request = etree.fromstring(data)
    try:
        ok, wsse_username = wsse.validate(request, url_config)
    except SecurityException, e:
        return AuthResult(False, AUTH_WSSE_VALIDATION_ERROR, e.description)
Esempio n. 8
0
def test_auth_result_properties():
    """ Tests that AuthResult's properties can be read correctly.
    """
    # Check the defaults first.
    a1 = AuthResult()
    eq_(False, a1.status)
    eq_('-1', a1.code)
    eq_('', a1.description)

    status, code, description = [uuid4().hex for x in range(3)]

    a2 = AuthResult(status, code, description)
    eq_(status, a2.status)
    eq_(code, a2.code)
    eq_(description, a2.description)
Esempio n. 9
0
def on_basic_auth(env, url_config, needs_auth_info=True):
    """ Visit _RequestApp._on_basic_auth method's docstring.
    """
    username = url_config['basic-auth-username']
    result = _on_basic_auth(env.get('HTTP_AUTHORIZATION', ''), username, url_config['basic-auth-password'])
    is_success = result is True # Yes, need to check for True
    
    auth_result = AuthResult(is_success)
        
    if is_success: 
        if needs_auth_info:
            auth_result.auth_info = {b'basic-auth-username': quote_plus(username).encode('utf-8')}
    else:
        auth_result.code = result
        
    return auth_result
Esempio n. 10
0
def on_basic_auth(env, url_config, needs_auth_info=True):
    """ Visit _RequestApp._on_basic_auth method's docstring.
    """
    username = url_config['basic-auth-username']
    result = _on_basic_auth(env.get('HTTP_AUTHORIZATION', ''), username, url_config['basic-auth-password'])
    is_success = result is True # Yes, need to check for True
    
    auth_result = AuthResult(is_success)
        
    if is_success: 
        if needs_auth_info:
            auth_result.auth_info = {b'basic-auth-username': quote_plus(username).encode('utf-8')}
    else:
        auth_result.code = result
        
    return auth_result
Esempio n. 11
0
def test_auth_result_repr():
    """ Tests the AuthResult's __repr__ output.
    """
    at_pattern = '\w*'
    status, code, description = [uuid4().hex for x in range(3)]
    auth_info = {b'abc': b'def'}
    a1 = AuthResult(status, code, description)
    a1.auth_info = auth_info
    r = repr(a1)

    pattern = '<AuthResult at {0} status={1} code={2} description={3} auth_info={{abc: def}}\n>'
    pattern = pattern.format(at_pattern, status, code, description)

    regexp = re.compile(pattern)

    assert_true(regexp.match(r) is not None, (pattern, r))
Esempio n. 12
0
def test_auth_result_repr():
    """ Tests the AuthResult's __repr__ output.
    """
    at_pattern = '\w*'
    status, code, description = [uuid4().hex for x in range(3)]
    auth_info = {b'abc':b'def'}
    a1 = AuthResult(status, code, description)
    a1.auth_info = auth_info
    r = repr(a1)

    pattern = '<AuthResult at {0} status={1} code={2} description={3} auth_info={{abc: def}}\n>'
    pattern = pattern.format(at_pattern, status, code, description)

    regexp = re.compile(pattern)

    assert_true(regexp.match(r) is not None, (pattern, r))
Esempio n. 13
0
def on_ssl_cert(url_config, client_cert, field_prefix, needs_auth_info=True):
    """ Visit _RequestApp._on_ssl_cert method's docstring.
    """
    if client_cert:
        config_fields = {}
        for field, value in url_config.items():
            if field.startswith(field_prefix):
                config_fields[field.split(field_prefix)[1]] = value

        # There are no fields so the user just wants the connection be
        # encrypted and the client use client certificate however they're
        # not interested in the cert's fields - so as long as the CA is
        # OK (and we know it is because otherwise we wouldn't have gotten
        # so far), we let the client in.
        if not config_fields:
            return True
        else:
            subject =  client_cert.get('subject')
            if not subject:
                return AuthResult(False, AUTH_CERT_NO_SUBJECT)

            cert_fields = dict((elem[0][0].encode('utf-8'), elem[0][1].encode('utf-8')) for elem in subject)
            
            for config_field, config_value in config_fields.items():
                cert_value = cert_fields.get(config_field)
                if not cert_value:
                    return AuthResult(False, AUTH_CERT_NO_VALUE)
                if cert_value != config_value:
                    return AuthResult(False, AUTH_CERT_VALUE_MISMATCH)
            else:
                auth_result = AuthResult(True, '0')
                if needs_auth_info:
                    auth_result.auth_info = dict((quote_plus(k), quote_plus(v)) for k, v in cert_fields.iteritems())

                return auth_result
Esempio n. 14
0
    def _on_digest_auth(self, env, url_config, *ignored):
        """ Handles HTTP Digest Authentication.
        """
        auth = env.get('HTTP_AUTHORIZATION')
        if not auth:
            return AuthResult(False, AUTH_DIGEST_NO_AUTH)

        auth = self._parse_digest_auth(auth)

        expected_username = url_config['digest-auth-username']
        expected_password = url_config['digest-auth-password']
        expected_realm = url_config['digest-auth-realm']

        if auth['username'] != expected_username:
            return AuthResult(False, AUTH_DIGEST_USERNAME_MISMATCH)

        if auth['realm'] != expected_realm:
            return AuthResult(False, AUTH_DIGEST_REALM_MISMATCH)

        if env.get('QUERY_STRING'):
            expected_uri = '{0}?{1}'.format(env['PATH_INFO'], env['QUERY_STRING'])
        else:
            expected_uri = env['PATH_INFO']

        if auth['uri'] != expected_uri:
            return AuthResult(False, AUTH_DIGEST_URI_MISMATCH)

        expected_response = self._compute_digest_auth_response(expected_username,
                                expected_realm, expected_password, expected_uri,
                                env['REQUEST_METHOD'], auth['nonce'])

        if auth['response'] == expected_response:
            auth_result = AuthResult(True, '0')
            auth_result._auth_info = {b'digest-auth-username':quote_plus(expected_username),
                                      b'digest-auth-realm':quote_plus(expected_realm)}

            return auth_result
        else:
            return AuthResult(False, AUTH_DIGEST_RESPONSE_MISMATCH)
Esempio n. 15
0
    def _on_ssl_cert(self, env, url_config, client_cert, data):
        """ Validates the client SSL/TLS certificates, its very existence and
        the values of its fields (commonName, organizationName etc.)
        """
        if client_cert:
            field_prefix = 'ssl-cert-'
            config_fields = {}
            for field, value in url_config.items():
                if field.startswith(field_prefix):
                    config_fields[field.split(field_prefix)[1]] = value

            # There are no fields so the user just wants the connection be
            # encrypted and the client use client certificate however they're
            # not interested in the cert's fields - so as long as the CA is
            # OK (and we know it is because otherwise we wouldn't have gotten
            # so far), we let the client in.
            if not config_fields:
                return True
            else:
                subject =  client_cert.get('subject')
                if not subject:
                    return AuthResult(False, AUTH_CERT_NO_SUBJECT)

                cert_fields = dict(elem[0] for elem in subject)

                for config_field, config_value in config_fields.items():
                    cert_value = cert_fields.get(config_field)
                    if not cert_value:
                        return AuthResult(False, AUTH_CERT_NO_VALUE)
                    if cert_value != config_value:
                        return AuthResult(False, AUTH_CERT_VALUE_MISMATCH)
                else:
                    auth_result = AuthResult(True, '0')
                    auth_result.auth_info = dict((quote_plus(k), quote_plus(v)) for k, v in cert_fields.iteritems())

                    return auth_result
Esempio n. 16
0
    def _on_basic_auth(self, env, url_config, *ignored):
        """ Handles HTTP Basic Authentication.
        """
        auth = env.get('HTTP_AUTHORIZATION')
        if not auth:
            return AuthResult(False, AUTH_BASIC_NO_AUTH)

        prefix = 'Basic '
        if not auth.startswith(prefix):
            return AuthResult(False, AUTH_BASIC_INVALID_PREFIX)

        _, auth = auth.split(prefix)
        auth = auth.strip().decode('base64')

        username, password = auth.split(':', 1)

        if username == url_config['basic-auth-username'] and \
           password == url_config['basic-auth-password']:
            auth_result = AuthResult(True, '0')
            auth_result.auth_info = {b'basic-auth-username': quote_plus(username)}

            return auth_result
        else:
            return AuthResult(False, AUTH_BASIC_USERNAME_OR_PASSWORD_MISMATCH)
Esempio n. 17
0
    def __call__(self,
                 env,
                 start_response,
                 client_cert=None,
                 client_cert_der=None):
        """ Finds the configuration for the given URL and passes the control on
        to the main request handler. In case no config for the given URL is
        found, a 404 Not Found will be returned to the calling side.
        """
        ctx = InvocationContext(self.instance_name, self.instance_unique,
                                next(self.msg_counter), self.now())
        ctx.auth_result = AuthResult()
        ctx.env = env

        ctx.client_cert = client_cert
        ctx.client_cert_der = client_cert_der

        if self.sign_invocation_id:
            h = hashlib.sha256()
            h.update('{0}:{1}'.format(self.instance_secret, ctx.invocation_id))
            ctx.invocation_id_signed = h.hexdigest()

        path_info = env['PATH_INFO']
        if self.quote_path_info:
            path_info = quote_plus(path_info)

        query_string = env.get('QUERY_STRING')
        if query_string:
            query_string = '?' + query_string
            if self.quote_query_string:
                query_string = quote_plus(query_string)

        ctx.path_info = path_info
        ctx.query_string = query_string
        ctx.remote_address = env.get('REMOTE_ADDR')
        ctx.request_method = env.get('REQUEST_METHOD')

        for c, url_config in self.urls:
            match = c.test(path_info)
            if match:
                ctx.url_config = url_config
                return self._on_request(ctx, start_response, env, url_config,
                                        client_cert, match)
        else:
            # No config for that URL, we can't let the client in.
            return self._404(ctx, start_response)
Esempio n. 18
0
    def _on_digest_auth(self, env, url_config, *ignored):
        """ Handles HTTP Digest Authentication.
        """
        auth = env.get('HTTP_AUTHORIZATION')
        if not auth:
            return AuthResult(False, AUTH_DIGEST_NO_AUTH)

        auth = self._parse_digest_auth(auth)

        expected_username = url_config['digest-auth-username']
        expected_password = url_config['digest-auth-password']
        expected_realm = url_config['digest-auth-realm']

        if auth['username'] != expected_username:
            return AuthResult(False, AUTH_DIGEST_USERNAME_MISMATCH)

        if auth['realm'] != expected_realm:
            return AuthResult(False, AUTH_DIGEST_REALM_MISMATCH)

        if env.get('QUERY_STRING'):
            expected_uri = '{0}?{1}'.format(env['PATH_INFO'],
                                            env['QUERY_STRING'])
        else:
            expected_uri = env['PATH_INFO']

        if auth['uri'] != expected_uri:
            return AuthResult(False, AUTH_DIGEST_URI_MISMATCH)

        expected_response = self._compute_digest_auth_response(
            expected_username, expected_realm, expected_password, expected_uri,
            env['REQUEST_METHOD'], auth['nonce'])

        if auth['response'] == expected_response:
            auth_result = AuthResult(True, '0')
            auth_result._auth_info = {
                b'digest-auth-username': quote_plus(expected_username),
                b'digest-auth-realm': quote_plus(expected_realm)
            }

            return auth_result
        else:
            return AuthResult(False, AUTH_DIGEST_RESPONSE_MISMATCH)
Esempio n. 19
0
def test_invocation_context_format_log_message():
    """ Tests the correctness of formatting of logging messages.
    """
    _auth1 = AuthResult(True)
    _auth2 = AuthResult(False, uuid4().hex)

    for _auth_result in _auth1, _auth2:
        for _needs_details in True, False:

            _now = datetime.now()
            _start_to_ext_start = timedelta(seconds=1, microseconds=129)
            _ext_took = timedelta(seconds=3, microseconds=9017)
            _ext_end_to_proc_end = timedelta(seconds=7, microseconds=3511)

            _proc_start = _now
            _proc_end = _now + _start_to_ext_start + _ext_took + _ext_end_to_proc_end
            _ext_start = _now + _start_to_ext_start
            _ext_end = _now + _start_to_ext_start + _ext_took

            _env = {
                'HTTP_USER_AGENT': uuid4().hex,
                'SERVER_SOFTWARE': uuid4().hex,
                'SERVER_NAME': uuid4().hex,
                'SERVER_PORT': uuid4().hex
            }

            _code = uuid4().hex

            (_instance_name, _instance_unique, _message_number, _url_config,
             _client_cert, _data, _remote_address, _config_type, _path_info,
             _query_string, _client_address,
             _request_method) = [uuid4().hex for x in range(12)]

            ctx = InvocationContext(_instance_name, _instance_unique,
                                    _message_number, _proc_start, _proc_end,
                                    _ext_start, _ext_end, _env, _url_config,
                                    _client_cert, _data, _remote_address,
                                    _auth_result, _config_type, _path_info,
                                    _query_string, _client_address,
                                    _request_method)

            msg = ctx.format_log_message(_code, _needs_details)

            if _needs_details:

                (invocation_id, code, proc_start, remote_address, req_info,
                 secwall_overhead, ext_overhead, proc_total, auth_result,
                 auth_code, http_user_agent, server_software, server_name,
                 server_port, config_type, data) = msg.split(';')
            else:
                (invocation_id, code, proc_start, remote_address, req_info,
                 secwall_overhead, ext_overhead, proc_total, auth_result,
                 auth_code) = msg.split(';')

            eq_(invocation_id, ctx.invocation_id)
            eq_(code, _code)
            eq_(proc_start, str(_proc_start))
            eq_(remote_address, _remote_address)
            eq_(req_info, _request_method + ' ' + _path_info + _query_string)

            _proc_total = _proc_end - _proc_start
            _ext_overhead = _ext_end - _ext_start
            _secwall_overhead = _proc_total - _ext_overhead

            eq_(
                proc_total,
                str(_proc_total.seconds) + '.' +
                str(_proc_total.microseconds).zfill(6))
            eq_(
                ext_overhead,
                str(_ext_overhead.seconds) + '.' +
                str(_ext_overhead.microseconds).zfill(6))
            eq_(
                secwall_overhead,
                str(_secwall_overhead.seconds) + '.' +
                str(_secwall_overhead.microseconds).zfill(6))

            if _auth_result:
                eq_(auth_result, '0')
            else:
                eq_(auth_result, '1')

            eq_(auth_code, _auth_result.code)

            if _needs_details:
                eq_(http_user_agent,
                    '"{0}"'.format(_env.get('HTTP_USER_AGENT')))
                eq_(server_software, _env.get('SERVER_SOFTWARE'))
                eq_(server_name, _env.get('SERVER_NAME'))
                eq_(server_port, _env.get('SERVER_PORT'))
                eq_(config_type, _config_type)
                eq_(data, _data)
Esempio n. 20
0
                return auth_result

def on_wsse_pwd(wsse, url_config, data, needs_auth_info=True):
    """ Visit _RequestApp._on_wsse_pwd method's docstring.
    """
    if not data:
        return AuthResult(False, AUTH_WSSE_NO_DATA)

    request = etree.fromstring(data)
    try:
        ok, wsse_username = wsse.validate(request, url_config)
    except SecurityException, e:
        return AuthResult(False, AUTH_WSSE_VALIDATION_ERROR, e.description)
    else:
        auth_result = AuthResult(True, '0')
        if needs_auth_info:
            auth_result.auth_info = {b'wsse-pwd-username': str(wsse_username)}

        return auth_result
        
def _on_basic_auth(auth, expected_username, expected_password):
    """ A low-level call for checking the HTTP Basic Auth credentials.
    """
    if not auth:
        return AUTH_BASIC_NO_AUTH

    prefix = 'Basic '
    if not auth.startswith(prefix):
        return AUTH_BASIC_INVALID_PREFIX
Esempio n. 21
0
                return auth_result

def on_wsse_pwd(wsse, url_config, data, needs_auth_info=True):
    """ Visit _RequestApp._on_wsse_pwd method's docstring.
    """
    if not data:
        return AuthResult(False, AUTH_WSSE_NO_DATA)

    request = etree.fromstring(data)
    try:
        ok, wsse_username = wsse.validate(request, url_config)
    except SecurityException, e:
        return AuthResult(False, AUTH_WSSE_VALIDATION_ERROR, e.description)
    else:
        auth_result = AuthResult(True, '0')
        if needs_auth_info:
            auth_result.auth_info = {b'wsse-pwd-username': str(wsse_username)}

        return auth_result
        
def _on_basic_auth(auth, expected_username, expected_password):
    """ A low-level call for checking the HTTP Basic Auth credentials.
    """
    if not auth:
        return AUTH_BASIC_NO_AUTH

    prefix = 'Basic '
    if not auth.startswith(prefix):
        return AUTH_BASIC_INVALID_PREFIX
Esempio n. 22
0
    def _on_request(self, ctx, start_response, env, url_config, client_cert, match=None):
        """ Checks security, invokes the backend server, returns the response.
        """

        # If True, we know we're being accessed using SSL.
        has_ssl = False

        # Some quick SSL-related checks first.
        if url_config.get('ssl'):

            # Has the URL been accessed through SSL/TLS?
            if env.get('wsgi.url_scheme') != 'https':
                return self._403(ctx, start_response)

            # OK, we're now sure we're being invoked through SSL.
            has_ssl = True

            # Is the client cert required?
            if url_config.get('ssl-cert') and not client_cert:
                return self._401(ctx, start_response, self._get_www_auth(url_config, 'ssl-cert'))

        data = env['wsgi.input'].read()
        data = data if data else None
        ctx.data = data

        # ssl-wrap-only implies 'ssl':True but everyone's free to forget about
        # setting it so we may wind up ostensibly using SSL yet in reality
        # we'd be using plain HTTP. That's why there's an additional check
        # for 'has_ssl' below.
        if url_config.get('ssl-wrap-only'):
            if not has_ssl:
                return self._403(ctx, start_response)
            else:
                # There will be no authentication performed and we need to fill
                # in the information ourselves here so that what goes to logs
                # doesn't use the ctx's defaults.
                ctx.auth_result = AuthResult(True, '0')
        else:
            for config_type in self.config.validation_precedence:
                if config_type in url_config:

                    handler = getattr(self, '_on_' + config_type.replace('-', '_'))
                    auth_result = handler(env, url_config, client_cert, data)

                    ctx.auth_result = auth_result
                    ctx.config_type = config_type

                    if not auth_result:
                        return self._401(ctx, start_response, self._get_www_auth(url_config, config_type))
                    break
            else:
                return self._500(ctx, start_response)

        rewrite = url_config.get('rewrite')
        if rewrite:
            path_info = rewrite.format(**match[1])
        else:
            path_info = env['PATH_INFO']

        req = urllib2.Request(url_config['host'] + path_info, data)

        from_client_ignore = url_config['from-client-ignore']
        to_backend_add = url_config['to-backend-add']

        # Pass the headers to the backend server unless they're to be ignored.
        for name in (name for name in env if name.startswith('HTTP_')):
            value = env[name]
            name = name.split('HTTP_')[1].replace('_', '-')
            if not name in from_client_ignore:
                req.add_header(name, value)

        # Custom headers to be sent to the backend server.
        for name, value in to_backend_add.iteritems():
            req.add_header(name, value)

        # Sign and return the invocation ID.
        if self.add_invocation_id:
            req.add_header('X-sec-wall-invocation-id', ctx.invocation_id)

        if self.sign_invocation_id:
            req.add_header('X-sec-wall-invocation-id-signed', ctx.invocation_id_signed)

        if url_config.get('add-auth-info', True):
            req.add_header('X-sec-wall-auth-info', ctx.auth_result.auth_info.strip())

        if url_config.get('sign-auth-info', True):
            h = hashlib.sha256()
            h.update('{0}:{1}:{2}'.format(ctx.invocation_id, self.instance_secret,
                                          ctx.auth_result.auth_info.strip()))
            req.add_header('X-sec-wall-auth-info-signed', h.hexdigest())

        try:
            opener = urllib2.build_opener()
            ctx.ext_start = self.now()
            resp = opener.open(req)
        except urllib2.HTTPError, e:
            resp = e