fix: http authorization leakage (#4146)

This commit is contained in:
Yeuoly 2024-05-07 16:56:25 +08:00 committed by GitHub
parent e353809680
commit d51f52a649
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 43 additions and 14 deletions

View File

@ -1,9 +1,13 @@
import os
from typing import Literal, Optional, Union from typing import Literal, Optional, Union
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from core.workflow.entities.base_node_data_entities import BaseNodeData from core.workflow.entities.base_node_data_entities import BaseNodeData
MAX_CONNECT_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_CONNECT_TIMEOUT', '300'))
MAX_READ_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_READ_TIMEOUT', '600'))
MAX_WRITE_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_WRITE_TIMEOUT', '600'))
class HttpRequestNodeData(BaseNodeData): class HttpRequestNodeData(BaseNodeData):
""" """
@ -36,9 +40,9 @@ class HttpRequestNodeData(BaseNodeData):
data: Union[None, str] data: Union[None, str]
class Timeout(BaseModel): class Timeout(BaseModel):
connect: int connect: int = MAX_CONNECT_TIMEOUT
read: int read: int = MAX_READ_TIMEOUT
write: int write: int = MAX_WRITE_TIMEOUT
method: Literal['get', 'post', 'put', 'patch', 'delete', 'head'] method: Literal['get', 'post', 'put', 'patch', 'delete', 'head']
url: str url: str
@ -46,4 +50,5 @@ class HttpRequestNodeData(BaseNodeData):
headers: str headers: str
params: str params: str
body: Optional[Body] body: Optional[Body]
timeout: Optional[Timeout] timeout: Optional[Timeout]
mask_authorization_header: Optional[bool] = True

View File

@ -19,7 +19,6 @@ READABLE_MAX_BINARY_SIZE = f'{MAX_BINARY_SIZE / 1024 / 1024:.2f}MB'
MAX_TEXT_SIZE = int(os.environ.get('HTTP_REQUEST_NODE_MAX_TEXT_SIZE', str(1024 * 1024))) # 10MB # 1MB MAX_TEXT_SIZE = int(os.environ.get('HTTP_REQUEST_NODE_MAX_TEXT_SIZE', str(1024 * 1024))) # 10MB # 1MB
READABLE_MAX_TEXT_SIZE = f'{MAX_TEXT_SIZE / 1024 / 1024:.2f}MB' READABLE_MAX_TEXT_SIZE = f'{MAX_TEXT_SIZE / 1024 / 1024:.2f}MB'
class HttpExecutorResponse: class HttpExecutorResponse:
headers: dict[str, str] headers: dict[str, str]
response: Union[httpx.Response, requests.Response] response: Union[httpx.Response, requests.Response]
@ -345,10 +344,13 @@ class HttpExecutor:
# validate response # validate response
return self._validate_and_parse_response(response) return self._validate_and_parse_response(response)
def to_raw_request(self) -> str: def to_raw_request(self, mask_authorization_header: Optional[bool] = True) -> str:
""" """
convert to raw request convert to raw request
""" """
if mask_authorization_header == None:
mask_authorization_header = True
server_url = self.server_url server_url = self.server_url
if self.params: if self.params:
server_url += f'?{urlencode(self.params)}' server_url += f'?{urlencode(self.params)}'
@ -357,6 +359,17 @@ class HttpExecutor:
headers = self._assembling_headers() headers = self._assembling_headers()
for k, v in headers.items(): for k, v in headers.items():
if mask_authorization_header:
# get authorization header
if self.authorization.type == 'api-key':
authorization_header = 'Authorization'
if self.authorization.config and self.authorization.config.header:
authorization_header = self.authorization.config.header
if k.lower() == authorization_header.lower():
raw_request += f'{k}: {"*" * len(v)}\n'
continue
raw_request += f'{k}: {v}\n' raw_request += f'{k}: {v}\n'
raw_request += '\n' raw_request += '\n'

View File

@ -1,5 +1,4 @@
import logging import logging
import os
from mimetypes import guess_extension from mimetypes import guess_extension
from os import path from os import path
from typing import cast from typing import cast
@ -9,14 +8,15 @@ from core.tools.tool_file_manager import ToolFileManager
from core.workflow.entities.node_entities import NodeRunResult, NodeType from core.workflow.entities.node_entities import NodeRunResult, NodeType
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.nodes.base_node import BaseNode from core.workflow.nodes.base_node import BaseNode
from core.workflow.nodes.http_request.entities import HttpRequestNodeData from core.workflow.nodes.http_request.entities import (
MAX_CONNECT_TIMEOUT,
MAX_READ_TIMEOUT,
MAX_WRITE_TIMEOUT,
HttpRequestNodeData,
)
from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse
from models.workflow import WorkflowNodeExecutionStatus from models.workflow import WorkflowNodeExecutionStatus
MAX_CONNECT_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_CONNECT_TIMEOUT', '300'))
MAX_READ_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_READ_TIMEOUT', '600'))
MAX_WRITE_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_WRITE_TIMEOUT', '600'))
HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeData.Timeout(connect=min(10, MAX_CONNECT_TIMEOUT), HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeData.Timeout(connect=min(10, MAX_CONNECT_TIMEOUT),
read=min(60, MAX_READ_TIMEOUT), read=min(60, MAX_READ_TIMEOUT),
write=min(20, MAX_WRITE_TIMEOUT)) write=min(20, MAX_WRITE_TIMEOUT))
@ -63,7 +63,9 @@ class HttpRequestNode(BaseNode):
process_data = {} process_data = {}
if http_executor: if http_executor:
process_data = { process_data = {
'request': http_executor.to_raw_request(), 'request': http_executor.to_raw_request(
mask_authorization_header=node_data.mask_authorization_header
),
} }
return NodeRunResult( return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED, status=WorkflowNodeExecutionStatus.FAILED,
@ -82,7 +84,9 @@ class HttpRequestNode(BaseNode):
'files': files, 'files': files,
}, },
process_data={ process_data={
'request': http_executor.to_raw_request(), 'request': http_executor.to_raw_request(
mask_authorization_header=node_data.mask_authorization_header
),
} }
) )

View File

@ -38,6 +38,7 @@ def test_get(setup_http_mock):
'headers': 'X-Header:123', 'headers': 'X-Header:123',
'params': 'A:b', 'params': 'A:b',
'body': None, 'body': None,
'mask_authorization_header': False,
} }
}, **BASIC_NODE_DATA) }, **BASIC_NODE_DATA)
@ -95,6 +96,7 @@ def test_custom_authorization_header(setup_http_mock):
'headers': 'X-Header:123', 'headers': 'X-Header:123',
'params': 'A:b', 'params': 'A:b',
'body': None, 'body': None,
'mask_authorization_header': False,
} }
}, **BASIC_NODE_DATA) }, **BASIC_NODE_DATA)
@ -126,6 +128,7 @@ def test_template(setup_http_mock):
'headers': 'X-Header:123\nX-Header2:{{#a.b123.args2#}}', 'headers': 'X-Header:123\nX-Header2:{{#a.b123.args2#}}',
'params': 'A:b\nTemplate:{{#a.b123.args2#}}', 'params': 'A:b\nTemplate:{{#a.b123.args2#}}',
'body': None, 'body': None,
'mask_authorization_header': False,
} }
}, **BASIC_NODE_DATA) }, **BASIC_NODE_DATA)
@ -161,6 +164,7 @@ def test_json(setup_http_mock):
'type': 'json', 'type': 'json',
'data': '{"a": "{{#a.b123.args1#}}"}' 'data': '{"a": "{{#a.b123.args1#}}"}'
}, },
'mask_authorization_header': False,
} }
}, **BASIC_NODE_DATA) }, **BASIC_NODE_DATA)
@ -193,6 +197,7 @@ def test_x_www_form_urlencoded(setup_http_mock):
'type': 'x-www-form-urlencoded', 'type': 'x-www-form-urlencoded',
'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}' 'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}'
}, },
'mask_authorization_header': False,
} }
}, **BASIC_NODE_DATA) }, **BASIC_NODE_DATA)
@ -225,6 +230,7 @@ def test_form_data(setup_http_mock):
'type': 'form-data', 'type': 'form-data',
'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}' 'data': 'a:{{#a.b123.args1#}}\nb:{{#a.b123.args2#}}'
}, },
'mask_authorization_header': False,
} }
}, **BASIC_NODE_DATA) }, **BASIC_NODE_DATA)
@ -260,6 +266,7 @@ def test_none_data(setup_http_mock):
'type': 'none', 'type': 'none',
'data': '123123123' 'data': '123123123'
}, },
'mask_authorization_header': False,
} }
}, **BASIC_NODE_DATA) }, **BASIC_NODE_DATA)