From cddbe6b7acb2edfb104d34a6968d46205aceb86c Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:16:47 +0530 Subject: [PATCH 01/15] move result update status logic to post processor module --- src/offat/tester/post_test_processor.py | 49 +++++++++++++++++++------ src/offat/tester/test_runner.py | 15 +------- src/offat/tester/tester_utils.py | 6 +++ 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/offat/tester/post_test_processor.py b/src/offat/tester/post_test_processor.py index 5e4539f..1f5b591 100644 --- a/src/offat/tester/post_test_processor.py +++ b/src/offat/tester/post_test_processor.py @@ -69,17 +69,44 @@ def re_match(patterns:list[str], endpoint:str) -> bool: @staticmethod - # TODO: use this everywhere instead of filtering data - def filter_status_code_based_results(result): - new_result = deepcopy(result) - if result.get('response_status_code') in result.get('success_codes'): - res_status = False # test failed - else: - res_status = True # test passed - new_result['result'] = res_status - new_result['result_details'] = result['result_details'].get(res_status) - - return new_result + def filter_status_code_based_results(results:list[dict]) -> list[dict]: # take a list and filter all at once + new_results = [] + for result in results: + new_result = deepcopy(result) + response_status_code = result.get('response_status_code') + success_codes = result.get('success_codes') + + # if response status code or success code is not + # found then continue updating status of remaining + # results + if not response_status_code or not success_codes: + continue + + if response_status_code in success_codes: + res_status = False # test failed + else: + res_status = True # test passed + + new_result['result'] = res_status + + # new_result['result_details'] = result['result_details'].get(res_status) + + new_results.append(new_result) + + return new_results + + + @staticmethod + def update_result_details(results:list[dict]): + new_results = [] + for result in results: + new_result = deepcopy(result) + new_result['result_details'] = result['result_details'].get(result['result']) + + new_results.append(new_result) + + return new_results + @staticmethod def matcher(results:list[dict]): diff --git a/src/offat/tester/test_runner.py b/src/offat/tester/test_runner.py index aca9965..ada0ad5 100644 --- a/src/offat/tester/test_runner.py +++ b/src/offat/tester/test_runner.py @@ -71,7 +71,7 @@ def _generate_payloads(self, params:list[dict], payload_for:PayloadFor=PayloadFo return {} - async def status_code_filter_request(self, test_task): + async def send_request(self, test_task): url = test_task.get('url') http_method = test_task.get('method') success_codes = test_task.get('success_codes', [200, 301]) @@ -91,14 +91,7 @@ async def status_code_filter_request(self, test_task): except ConnectionRefusedError: logger.error('Connection Failed! Server refused Connection!!') - # TODO: move this filter to result processing module test_result = test_task - if isinstance(response, dict) and response.get('status') in success_codes: - result = False # test failed - else: - result = True # test passed - test_result['result'] = result - test_result['result_details'] = test_result['result_details'].get(result) # add request headers to result test_result['request_headers'] = response.get('req_headers',[]) @@ -131,13 +124,9 @@ async def run_tests(self, test_tasks:list): tasks = [] for test_task in test_tasks: - match test_task.get('response_filter', None): - case _: # default filter - task_filter = self.status_code_filter_request - tasks.append( ensure_future( - task_filter(test_task) + self.send_request(test_task) ) ) diff --git a/src/offat/tester/tester_utils.py b/src/offat/tester/tester_utils.py index d1d1960..28c817a 100644 --- a/src/offat/tester/tester_utils.py +++ b/src/offat/tester/tester_utils.py @@ -37,6 +37,12 @@ def run_test(test_runner:TestRunner, tests:list[dict], regex_pattern:str=None, s if post_run_matcher_test: test_results = PostRunTests.matcher(test_results) + + # update test result for status based code filter + test_results = PostRunTests.filter_status_code_based_results(test_results) + + # update tests result success/failure details + test_results = PostRunTests.update_result_details(test_results) results = test_table_generator.generate_result_table(deepcopy(test_results)) print(results) From f9d26a40b6af4c9b899f4441a712bbe78d1f2d7f Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sun, 22 Oct 2023 16:06:05 +0530 Subject: [PATCH 02/15] move data exposure test to post test processor module --- src/offat/tester/data_exposure.py | 37 ---------------------- src/offat/tester/post_test_processor.py | 41 +++++++++++++++++++++++-- src/offat/tester/test_runner.py | 4 +-- src/offat/tester/tester_utils.py | 4 +++ 4 files changed, 43 insertions(+), 43 deletions(-) delete mode 100644 src/offat/tester/data_exposure.py diff --git a/src/offat/tester/data_exposure.py b/src/offat/tester/data_exposure.py deleted file mode 100644 index 5c36123..0000000 --- a/src/offat/tester/data_exposure.py +++ /dev/null @@ -1,37 +0,0 @@ -from re import findall -from .regexs import sensitive_data_regex_patterns - - -def detect_data_exposure(data:str)->dict: - '''Detects data exposure against sensitive data regex - patterns and returns dict of matched results - - Args: - data (str): data to be analyzed for exposure - - Returns: - dict: dictionary with tag as dict key and matched pattern as dict value - ''' - # Dictionary to store detected data exposures - detected_exposures = {} - - for pattern_name, pattern in sensitive_data_regex_patterns.items(): - matches = findall(pattern, data) - if matches: - detected_exposures[pattern_name] = matches - - return detected_exposures - - -if __name__ == '__main__': - from json import dumps - sample_test_data = dumps({ - "message" : "Please do not share your AWS Access Key: AKIAEXAMPLEKEY, AWS Secret Key: 9hsk24mv8wzJ3/78mx3p5x3E7N0P39n6Zq0RxTee, Aadhaar: 1234 5678 9012, PAN: ABCDE1234F, SSN: 123-45-6789, credit card: 1234-5678-9012-3456, or email: john.doe@example.com. You can reach me at +1 (555) 123-4567 or via email at contact@example.com. The event date is scheduled for 01/25/2023. The server IP is 192.168.1.1, and IPv6 is 2001:0db8:85a3:0000:0000:8a2e:0370:7334. Password examples: Passw0rd!, Strong@123, mySecret12#. My VISA Card: 4001778837951872" - }) - - # detect data exposure - exposures = detect_data_exposure(sample_test_data) - - # Display the detected exposures - for data_type, data_values in exposures.items(): - print(f"Detected {data_type}: {data_values}") \ No newline at end of file diff --git a/src/offat/tester/post_test_processor.py b/src/offat/tester/post_test_processor.py index 1f5b591..6c6ae83 100644 --- a/src/offat/tester/post_test_processor.py +++ b/src/offat/tester/post_test_processor.py @@ -1,9 +1,10 @@ from copy import deepcopy -from re import search as re_search -from pprint import pprint +from re import search as re_search, findall +from .regexs import sensitive_data_regex_patterns from .test_runner import TestRunnerFiltersEnum + class PostRunTests: '''class Includes tests that should be ran after running all the active test''' @staticmethod @@ -66,6 +67,40 @@ def re_match(patterns:list[str], endpoint:str) -> bool: actor_based_tests.append(PostRunTests.filter_status_code_based_results(actor_test_result)) return actor_based_tests + + + @staticmethod + def detect_data_exposure(results:list[dict])->list[dict]: + '''Detects data exposure against sensitive data regex + patterns and returns dict of matched results + + Args: + data (str): data to be analyzed for exposure + + Returns: + dict: dictionary with tag as dict key and matched pattern as dict value + ''' + def detect_exposure(data:str) -> dict: + # Dictionary to store detected data exposures + detected_exposures = {} + + for pattern_name, pattern in sensitive_data_regex_patterns.items(): + matches = findall(pattern, data) + if matches: + detected_exposures[pattern_name] = matches + return detected_exposures + + + new_results = [] + + for result in results: + res_body = result.get('response_body') + data_exposures_dict = detect_exposure(str(res_body)) + result['data_leak'] = data_exposures_dict + new_results.append(result) + + return new_results + @staticmethod @@ -104,7 +139,7 @@ def update_result_details(results:list[dict]): new_result['result_details'] = result['result_details'].get(result['result']) new_results.append(new_result) - + return new_results diff --git a/src/offat/tester/test_runner.py b/src/offat/tester/test_runner.py index ada0ad5..a210fb2 100644 --- a/src/offat/tester/test_runner.py +++ b/src/offat/tester/test_runner.py @@ -1,6 +1,5 @@ from asyncio import ensure_future, gather from enum import Enum -from .data_exposure import detect_data_exposure from ..http import AsyncRequests, AsyncRLRequests from ..logger import create_logger @@ -105,8 +104,7 @@ async def send_request(self, test_task): # run data leak test # TODO: run this test in result processing module - data_exposures_dict = detect_data_exposure(str(res_body)) - test_result['data_leak'] = data_exposures_dict + # if data_exposures_dict: # print(res_body) diff --git a/src/offat/tester/tester_utils.py b/src/offat/tester/tester_utils.py index 28c817a..52b6d01 100644 --- a/src/offat/tester/tester_utils.py +++ b/src/offat/tester/tester_utils.py @@ -44,6 +44,10 @@ def run_test(test_runner:TestRunner, tests:list[dict], regex_pattern:str=None, s # update tests result success/failure details test_results = PostRunTests.update_result_details(test_results) + # run data leak tests + test_results = PostRunTests.detect_data_exposure(test_results) + + # print results results = test_table_generator.generate_result_table(deepcopy(test_results)) print(results) return test_results From 94276ed5c6e63605a71ed7f1310f777a374afa30 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Mon, 23 Oct 2023 00:54:17 +0530 Subject: [PATCH 03/15] remove resolved TODO comments --- src/offat/config_data_handler.py | 2 +- src/offat/tester/test_generator.py | 5 ----- src/offat/tester/test_runner.py | 12 ------------ 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/offat/config_data_handler.py b/src/offat/config_data_handler.py index 57393ca..0e2189f 100644 --- a/src/offat/config_data_handler.py +++ b/src/offat/config_data_handler.py @@ -40,7 +40,7 @@ def populate_user_data(actor_data:dict, actor_name:str,tests:list[dict]): request_headers[header.get('name')] = header.get('value') for test in tests: - # TODO: replace key and value instead of appending + # replace key and value instead of appending test['body_params'] += body_params test['query_params'] += query_params test['path_params'] += path_params diff --git a/src/offat/tester/test_generator.py b/src/offat/tester/test_generator.py index 6621f4c..ef6018d 100644 --- a/src/offat/tester/test_generator.py +++ b/src/offat/tester/test_generator.py @@ -316,18 +316,13 @@ def bola_fuzz_path_test( path_params_in_body = list(filter(lambda x: x.get('in') == 'path', request_params)) path_params += path_params_in_body path_params = fill_params(path_params) - # print(path_params) - # print('-'*30) for path_param in path_params: path_param_name = path_param.get('name') path_param_value = path_param.get('value') endpoint_path = endpoint_path.replace('{' + str(path_param_name) + '}', str(path_param_value)) - # TODO: handle request query params request_query_params = list(filter(lambda x: x.get('in') == 'query', request_params)) - # print(request_query_params) - # print('-'*30) tasks.append({ 'test_name':'BOLA Path Test with Fuzzed Params', diff --git a/src/offat/tester/test_runner.py b/src/offat/tester/test_runner.py index a210fb2..548f1f4 100644 --- a/src/offat/tester/test_runner.py +++ b/src/offat/tester/test_runner.py @@ -102,18 +102,6 @@ async def send_request(self, test_task): test_result['response_status_code'] = response.get('status') test_result['redirection'] = response.get('res_redirection', '') - # run data leak test - # TODO: run this test in result processing module - - - # if data_exposures_dict: - # print(res_body) - # Display the detected exposures - # for data_type, data_values in data_exposures_dict.items(): - # print(f"Detected {data_type}: {data_values}") - # print('--'*30) - - return test_result From 616a013ad0a753a8b3dcbf0e09765fa5cb7d6622 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Mon, 23 Oct 2023 01:22:01 +0530 Subject: [PATCH 04/15] bump project version to 0.9.1 --- src/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyproject.toml b/src/pyproject.toml index 71ca26c..8259bf1 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "offat" -version = "0.9.0" +version = "0.9.1" description = "Offensive API tester tool automates checks for common API vulnerabilities" authors = ["Dhrumil Mistry "] license = "MIT" From d61f79c5299cec7fd461131a4babd54857c845ac Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:07:04 +0530 Subject: [PATCH 05/15] add proxy support allow redirects by default update README with usage instructions --- src/README.md | 7 +++++++ src/offat/__main__.py | 4 ++++ src/offat/http.py | 35 +++++++++++++++++++++----------- src/offat/tester/test_runner.py | 8 ++++---- src/offat/tester/tester_utils.py | 7 +++++-- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/README.md b/src/README.md index 31263b6..d2e138f 100644 --- a/src/README.md +++ b/src/README.md @@ -25,6 +25,7 @@ Automatically Tests for vulnerabilities after generating tests from openapi spec - User Config Based Testing - API for Automating tests and Integrating Tool with other platforms/tools - CLI tool +- Proxy Support - Dockerized Project for Easy Usage - Open Source Tool with MIT License @@ -164,6 +165,12 @@ The disclaimer advises users to use the open-source project for ethical and legi > `rl`: requests rate limit, `dr`: delay between requests +- Use along with proxy + +```bash +offat -f swagger_file.json -p http://localhost:8080 --no-ssl -o output.json +``` + - Use user provided inputs for generating tests ```bash diff --git a/src/offat/__main__.py b/src/offat/__main__.py index 4dc6790..5af4181 100644 --- a/src/offat/__main__.py +++ b/src/offat/__main__.py @@ -40,6 +40,8 @@ def start(): parser.add_argument('-o', '--output', dest='output_file', type=str, help='path to store test results in json format', required=False, default=None) parser.add_argument('-H', '--headers', dest='headers', type=str, help='HTTP requests headers that should be sent during testing eg: User-Agent: offat', required=False, default=None, action='append', nargs='*') parser.add_argument('-tdc','--test-data-config', dest='test_data_config',help='YAML file containing user test data for tests', required=False, type=str) + parser.add_argument('-p', '--proxy', dest='proxy', help='Proxy server URL to route HTTP requests through (e.g., "http://proxyserver:port")', required=False, type=str) + parser.add_argument('-ns', '--no-ssl', dest='no_ssl', help='Ignores SSL verification when enabled', action='store_true', required=False) # False -> ignore SSL, True -> enforce SSL check args = parser.parse_args() @@ -72,6 +74,8 @@ def start(): rate_limit=rate_limit, delay=delay_rate, test_data_config=test_data_config, + proxy=args.proxy, + ssl=args.no_ssl, ) diff --git a/src/offat/http.py b/src/offat/http.py index dce19da..30ff6ec 100644 --- a/src/offat/http.py +++ b/src/offat/http.py @@ -1,4 +1,4 @@ -from aiohttp import ClientSession, ClientResponse +from aiohttp import ClientSession, ClientResponse, TCPConnector from os import name as os_name @@ -14,16 +14,21 @@ class AsyncRequests: AsyncRequests class helps to send HTTP requests. ''' - def __init__(self, headers: dict = None) -> None: + def __init__(self, headers: dict = None, proxy:str = None, ssl:bool=True, allow_redirects: bool=True) -> None: '''AsyncRequests class constructor Args: headers (dict): overrides default headers while sending HTTP requests + proxy (str): proxy URL to be used while sending requests + ssl (bool): ignores few SSL errors if value is False Returns: None ''' self._headers = headers + self._proxy = proxy if proxy else None + self._ssl = ssl if ssl else None + self._allow_redirects = allow_redirects async def request(self, url: str, method: str = 'GET', session: ClientSession = None, *args, **kwargs) -> ClientResponse: '''Send HTTP requests asynchronously @@ -33,31 +38,36 @@ async def request(self, url: str, method: str = 'GET', session: ClientSession = method (str): HTTP methods (default: GET) supports GET, POST, PUT, HEAD, OPTIONS, DELETE session (aiohttp.ClientSession): aiohttp Client Session for sending requests + Returns: dict: returns request and response data as dict ''' is_new_session = False + + + connector = TCPConnector(ssl=self._ssl,) + if not session: - session = ClientSession(headers=self._headers) + session = ClientSession(headers=self._headers, connector=connector) is_new_session = True method = str(method).upper() match method: case 'GET': - sent_req = session.get(url, *args, **kwargs) + sent_req = session.get(url, proxy=self._proxy, allow_redirects=self._allow_redirects, *args, **kwargs) case 'POST': - sent_req = session.post(url, *args, **kwargs) + sent_req = session.post(url, proxy=self._proxy, allow_redirects=self._allow_redirects, *args, **kwargs) case 'PUT': - sent_req = session.put(url, *args, **kwargs) + sent_req = session.put(url, proxy=self._proxy, allow_redirects=self._allow_redirects, *args, **kwargs) case 'PATCH': - sent_req = session.patch(url, *args, **kwargs) + sent_req = session.patch(url, proxy=self._proxy, allow_redirects=self._allow_redirects, *args, **kwargs) case 'HEAD': - sent_req = session.head(url, *args, **kwargs) + sent_req = session.head(url, proxy=self._proxy, allow_redirects=self._allow_redirects, *args, **kwargs) case 'OPTIONS': - sent_req = session.options(url, *args, **kwargs) + sent_req = session.options(url, proxy=self._proxy, allow_redirects=self._allow_redirects, *args, **kwargs) case 'DELETE': - sent_req = session.delete(url, *args, **kwargs) + sent_req = session.delete(url, proxy=self._proxy, allow_redirects=self._allow_redirects, *args, **kwargs) resp_data = None async with sent_req as response: @@ -84,7 +94,7 @@ class AsyncRLRequests(AsyncRequests): Send Asynchronous rate limited HTTP requests. ''' - def __init__(self, rate_limit: int = 20, delay: float = 0.05, headers: dict = None) -> None: + def __init__(self, rate_limit: int = 20, delay: float = 0.05, headers: dict = None, proxy: str = None, ssl:bool = True) -> None: '''AsyncRLRequests constructor Args: @@ -100,7 +110,8 @@ def __init__(self, rate_limit: int = 20, delay: float = 0.05, headers: dict = No self._delay = delay self._semaphore = asyncio.Semaphore(rate_limit) - super().__init__(headers) + super().__init__(headers=headers, proxy=proxy, ssl=ssl) + async def request(self, url: str, method: str = 'GET', session: ClientSession = None, *args, **kwargs) -> ClientResponse: '''Send HTTP requests asynchronously with rate limit and delay between the requests diff --git a/src/offat/tester/test_runner.py b/src/offat/tester/test_runner.py index 548f1f4..1ff7200 100644 --- a/src/offat/tester/test_runner.py +++ b/src/offat/tester/test_runner.py @@ -18,12 +18,12 @@ class PayloadFor(Enum): class TestRunner: - def __init__(self, rate_limit:int=None, delay:float=None, headers:dict=None) -> None: + def __init__(self, rate_limit:int=None, delay:float=None, headers:dict=None, proxy: str = None, ssl: bool = True) -> None: if rate_limit and delay: - self._client = AsyncRLRequests(rate_limit=rate_limit, delay=delay, headers=headers) + self._client = AsyncRLRequests(rate_limit=rate_limit, delay=delay, headers=headers, proxy=proxy, ssl=ssl) else: - self._client = AsyncRequests(headers=headers) - + self._client = AsyncRequests(headers=headers, proxy=proxy, ssl=ssl) + def _generate_payloads(self, params:list[dict], payload_for:PayloadFor=PayloadFor.BODY): '''Generate body payload from passed data for HTTP body and query. diff --git a/src/offat/tester/tester_utils.py b/src/offat/tester/tester_utils.py index 52b6d01..67fdc31 100644 --- a/src/offat/tester/tester_utils.py +++ b/src/offat/tester/tester_utils.py @@ -53,13 +53,16 @@ def run_test(test_runner:TestRunner, tests:list[dict], regex_pattern:str=None, s return test_results -def generate_and_run_tests(api_parser:OpenAPIParser, regex_pattern:str=None, output_file:str=None, rate_limit:int=None,delay:float=None,req_headers:dict=None, test_data_config:dict=None): +# Note: redirects are allowed by default making it easier for pentesters/researchers +def generate_and_run_tests(api_parser:OpenAPIParser, regex_pattern:str=None, output_file:str=None, rate_limit:int=None,delay:float=None,req_headers:dict=None,proxy:str = None, ssl:bool = True, test_data_config:dict=None): global test_table_generator, logger test_runner = TestRunner( rate_limit=rate_limit, delay=delay, - headers=req_headers + headers=req_headers, + proxy=proxy, + ssl=ssl, ) results:list = [] From dba7e11bc6523d1a63f5295fcddc7122804f7b66 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:22:19 +0530 Subject: [PATCH 06/15] add offat-api script to start api server --- src/offat/api/__main__.py | 8 +++++--- src/pyproject.toml | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/offat/api/__main__.py b/src/offat/api/__main__.py index 3131a0e..924aa49 100644 --- a/src/offat/api/__main__.py +++ b/src/offat/api/__main__.py @@ -1,11 +1,13 @@ from uvicorn import run - -if __name__ == '__main__': +def start(): run( app='offat.api.app:app', host="0.0.0.0", port=8000, workers=2, reload=True - ) \ No newline at end of file + ) + +if __name__ == '__main__': + start() \ No newline at end of file diff --git a/src/pyproject.toml b/src/pyproject.toml index 8259bf1..e0e8484 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -35,6 +35,7 @@ api = ["fastapi", "uvicorn", "redis", "rq", "python-dotenv"] [tool.poetry.scripts] offat = "offat.__main__:start" +offat-api = "offat.api.__main__:start" [build-system] From c10a2f7d16cdfd24597211d712f6ea57258c0791 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:24:45 +0530 Subject: [PATCH 07/15] bump project version to 0.10.0 --- src/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyproject.toml b/src/pyproject.toml index e0e8484..60e47cf 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "offat" -version = "0.9.1" +version = "0.10.0" description = "Offensive API tester tool automates checks for common API vulnerabilities" authors = ["Dhrumil Mistry "] license = "MIT" From 09becd5110764adfbbf05ec62083de2ed924b8cb Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:58:25 +0530 Subject: [PATCH 08/15] change logic for rate limiting use aiohttp TCP connector option for rate limiting --- src/offat/http.py | 58 +++++++-------------------------- src/offat/tester/test_runner.py | 9 ++--- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/src/offat/http.py b/src/offat/http.py index 30ff6ec..c6e39ab 100644 --- a/src/offat/http.py +++ b/src/offat/http.py @@ -11,13 +11,15 @@ class AsyncRequests: ''' - AsyncRequests class helps to send HTTP requests. + AsyncRequests class helps to send HTTP requests with rate limiting options. ''' - def __init__(self, headers: dict = None, proxy:str = None, ssl:bool=True, allow_redirects: bool=True) -> None: + def __init__(self, rate_limit:int=None, delay:float=None, headers: dict = None, proxy:str = None, ssl:bool=True, allow_redirects: bool=True) -> None: '''AsyncRequests class constructor Args: + rate_limit (int): number of concurrent requests at the same time + delay (float): delay between consecutive requests headers (dict): overrides default headers while sending HTTP requests proxy (str): proxy URL to be used while sending requests ssl (bool): ignores few SSL errors if value is False @@ -25,11 +27,14 @@ def __init__(self, headers: dict = None, proxy:str = None, ssl:bool=True, allow_ Returns: None ''' + self._rate_limit = rate_limit + self._delay = delay self._headers = headers self._proxy = proxy if proxy else None self._ssl = ssl if ssl else None self._allow_redirects = allow_redirects + async def request(self, url: str, method: str = 'GET', session: ClientSession = None, *args, **kwargs) -> ClientResponse: '''Send HTTP requests asynchronously @@ -44,9 +49,7 @@ async def request(self, url: str, method: str = 'GET', session: ClientSession = dict: returns request and response data as dict ''' is_new_session = False - - - connector = TCPConnector(ssl=self._ssl,) + connector = TCPConnector(ssl=self._ssl,limit=self._rate_limit,) if not session: session = ClientSession(headers=self._headers, connector=connector) @@ -86,46 +89,7 @@ async def request(self, url: str, method: str = 'GET', session: ClientSession = await session.close() del session - return resp_data - - -class AsyncRLRequests(AsyncRequests): - ''' - Send Asynchronous rate limited HTTP requests. - ''' - - def __init__(self, rate_limit: int = 20, delay: float = 0.05, headers: dict = None, proxy: str = None, ssl:bool = True) -> None: - '''AsyncRLRequests constructor - - Args: - rate_limit (int): number of concurrent requests at the same time - delay (float): delay between consecutive requests - headers (dict): overrides default headers while sending HTTP requests - - Returns: - None - ''' - assert isinstance(delay, float) or isinstance(delay, int) - assert isinstance(rate_limit, float) or isinstance(rate_limit, int) - - self._delay = delay - self._semaphore = asyncio.Semaphore(rate_limit) - super().__init__(headers=headers, proxy=proxy, ssl=ssl) - - - async def request(self, url: str, method: str = 'GET', session: ClientSession = None, *args, **kwargs) -> ClientResponse: - '''Send HTTP requests asynchronously with rate limit and delay between the requests - - Args: - url (str): URL of the webpage/endpoint - method (str): HTTP methods (default: GET) supports GET, POST, - PUT, HEAD, OPTIONS, DELETE - session (aiohttp.ClientSession): aiohttp Client Session for sending requests - - Returns: - dict: returns request and response data as dict - ''' - async with self._semaphore: - response = await super().request(url, method, session, *args, **kwargs) + if self._delay: await asyncio.sleep(self._delay) - return response + + return resp_data diff --git a/src/offat/tester/test_runner.py b/src/offat/tester/test_runner.py index 1ff7200..ef9ebc7 100644 --- a/src/offat/tester/test_runner.py +++ b/src/offat/tester/test_runner.py @@ -1,6 +1,6 @@ from asyncio import ensure_future, gather from enum import Enum -from ..http import AsyncRequests, AsyncRLRequests +from ..http import AsyncRequests from ..logger import create_logger logger = create_logger(__name__) @@ -19,11 +19,8 @@ class PayloadFor(Enum): class TestRunner: def __init__(self, rate_limit:int=None, delay:float=None, headers:dict=None, proxy: str = None, ssl: bool = True) -> None: - if rate_limit and delay: - self._client = AsyncRLRequests(rate_limit=rate_limit, delay=delay, headers=headers, proxy=proxy, ssl=ssl) - else: - self._client = AsyncRequests(headers=headers, proxy=proxy, ssl=ssl) - + self._client = AsyncRequests(rate_limit=rate_limit, delay=delay, headers=headers, proxy=proxy, ssl=ssl) + def _generate_payloads(self, params:list[dict], payload_for:PayloadFor=PayloadFor.BODY): '''Generate body payload from passed data for HTTP body and query. From 6388b216713ac98c860084b982d48885b2b5c945 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:35:38 +0530 Subject: [PATCH 09/15] use https scheme by defauly and handle proxy connection error use https if available handle proxy connection error --- src/offat/openapi.py | 3 ++- src/offat/tester/test_runner.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/offat/openapi.py b/src/offat/openapi.py index 8d5cc99..2e76638 100644 --- a/src/offat/openapi.py +++ b/src/offat/openapi.py @@ -19,7 +19,8 @@ def __init__(self, fpath:str, spec:dict=None) -> None: self._spec = spec self.host = self._spec.get('host') - self.base_url = f"http://{self.host}{self._spec.get('basePath','')}" + self.http_scheme = 'https' if 'https' in self._spec.get('schemes') else 'http' + self.base_url = f"{self.http_scheme}://{self.host}{self._spec.get('basePath','')}" self.request_response_params = self._get_request_response_params() diff --git a/src/offat/tester/test_runner.py b/src/offat/tester/test_runner.py index ef9ebc7..39e1db1 100644 --- a/src/offat/tester/test_runner.py +++ b/src/offat/tester/test_runner.py @@ -1,4 +1,5 @@ from asyncio import ensure_future, gather +from aiohttp.client_exceptions import ClientProxyConnectionError from enum import Enum from ..http import AsyncRequests from ..logger import create_logger @@ -86,12 +87,13 @@ async def send_request(self, test_task): response = await self._client.request(url=url, method=http_method, *args, **kwargs) except ConnectionRefusedError: logger.error('Connection Failed! Server refused Connection!!') + except ClientProxyConnectionError as e: + logger.error(f'Proxy Connection Error: {e}') test_result = test_task # add request headers to result test_result['request_headers'] = response.get('req_headers',[]) - # append response headers and body for analyzing data leak res_body = response.get('res_body', 'No Response Body Found') test_result['response_headers'] = response.get('res_headers') From 559c1334df1fd306d53246285914d56c183a8ca8 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 18:00:38 +0530 Subject: [PATCH 10/15] add gh actions for releases --- .github/workflows/dev-push.yml | 53 ++++++++++++++++++++++++++++++ .github/workflows/pypi-publish.yml | 39 ++++++++++++++++++++++ .github/workflows/release-push.yml | 48 +++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 .github/workflows/dev-push.yml create mode 100644 .github/workflows/pypi-publish.yml create mode 100644 .github/workflows/release-push.yml diff --git a/.github/workflows/dev-push.yml b/.github/workflows/dev-push.yml new file mode 100644 index 0000000..6ee4a4c --- /dev/null +++ b/.github/workflows/dev-push.yml @@ -0,0 +1,53 @@ +name: "Dev Release: Build and Push Docker Image to DockerHub" + +on: + push: + branches: + - "dev" + +jobs: + push-base-docker-image: + runs-on: ubuntu-latest + steps: + - name: Branch Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: ./DockerFiles/base-Dockerfile + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:dev + platforms: linux/amd64,linux/arm64 + + push-cli-docker-image: + runs-on: ubuntu-latest + steps: + - name: Branch Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: ./DockerFiles/cli-Dockerfile + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:dev + platforms: linux/amd64,linux/arm64 \ No newline at end of file diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 0000000..7a12b72 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,39 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload OWASP OFFAT Python Package to PyPi + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-push.yml b/.github/workflows/release-push.yml new file mode 100644 index 0000000..bf0faeb --- /dev/null +++ b/.github/workflows/release-push.yml @@ -0,0 +1,48 @@ +name: "Build and Push Docker Image to DockerHub" + +on: + release: + types: [published] + + +jobs: + push-base-docker-image: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64,linux/arm64 + push: true + file: ./src/Dockerfiles/base-Dockerfile + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:latest + + + push-cli-docker-image: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64,linux/arm64 + push: true + file: ./src/Dockerfiles/cli-Dockerfile + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:latest \ No newline at end of file From dc7abaed947b8073ac3b1fca3e707139e117fbc1 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 18:11:23 +0530 Subject: [PATCH 11/15] change directory to src before running futher stages --- .github/workflows/dev-push.yml | 2 ++ .github/workflows/pypi-publish.yml | 2 ++ .github/workflows/release-push.yml | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev-push.yml b/.github/workflows/dev-push.yml index 6ee4a4c..4edf573 100644 --- a/.github/workflows/dev-push.yml +++ b/.github/workflows/dev-push.yml @@ -20,6 +20,8 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: change cwd to src directory + run: cd src - name: Build and push uses: docker/build-push-action@v3 with: diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 7a12b72..ac0f76b 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -26,6 +26,8 @@ jobs: uses: actions/setup-python@v3 with: python-version: '3.x' + - name: change cwd to src directory + run: cd src - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/release-push.yml b/.github/workflows/release-push.yml index bf0faeb..748004f 100644 --- a/.github/workflows/release-push.yml +++ b/.github/workflows/release-push.yml @@ -18,12 +18,14 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: change cwd to src directory + run: cd src - name: Build and push uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64 push: true - file: ./src/Dockerfiles/base-Dockerfile + file: ./Dockerfiles/base-Dockerfile tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:latest From ad91b3232f5fe710b1800d2cbb8c1c5ad77a5429 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 18:37:43 +0530 Subject: [PATCH 12/15] test dev docker push action --- .github/workflows/dev-push.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dev-push.yml b/.github/workflows/dev-push.yml index 4edf573..fb4bdb3 100644 --- a/.github/workflows/dev-push.yml +++ b/.github/workflows/dev-push.yml @@ -20,13 +20,11 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: change cwd to src directory - run: cd src - name: Build and push uses: docker/build-push-action@v3 with: - context: . - file: ./DockerFiles/base-Dockerfile + context: ./src/ + file: ./src/DockerFiles/base-Dockerfile push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:dev platforms: linux/amd64,linux/arm64 @@ -48,8 +46,8 @@ jobs: - name: Build and push uses: docker/build-push-action@v3 with: - context: . - file: ./DockerFiles/cli-Dockerfile + context: ./src/ + file: ./src/DockerFiles/cli-Dockerfile push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:dev platforms: linux/amd64,linux/arm64 \ No newline at end of file From 478c3545dba50800bd16e3e9b4883ebeb7ebb51b Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 18:56:11 +0530 Subject: [PATCH 13/15] separate docker files for main and dev update dev-push workflow --- .github/workflows/dev-push.yml | 41 ++++++++++--------- src/DockerFiles/dev/backend-api-Dockerfile | 5 +++ .../dev/backend-api-worker-Dockerfile | 5 +++ src/DockerFiles/dev/cli-Dockerfile | 3 ++ .../{ => main}/backend-api-Dockerfile | 0 .../{ => main}/backend-api-worker-Dockerfile | 0 src/DockerFiles/{ => main}/cli-Dockerfile | 0 7 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 src/DockerFiles/dev/backend-api-Dockerfile create mode 100644 src/DockerFiles/dev/backend-api-worker-Dockerfile create mode 100644 src/DockerFiles/dev/cli-Dockerfile rename src/DockerFiles/{ => main}/backend-api-Dockerfile (100%) rename src/DockerFiles/{ => main}/backend-api-worker-Dockerfile (100%) rename src/DockerFiles/{ => main}/cli-Dockerfile (100%) diff --git a/.github/workflows/dev-push.yml b/.github/workflows/dev-push.yml index fb4bdb3..6a2d4e7 100644 --- a/.github/workflows/dev-push.yml +++ b/.github/workflows/dev-push.yml @@ -6,7 +6,7 @@ on: - "dev" jobs: - push-base-docker-image: + build-and-push-dev-docker-images: runs-on: ubuntu-latest steps: - name: Branch Checkout @@ -20,7 +20,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push + - name: Build and push offat-base docker image uses: docker/build-push-action@v3 with: context: ./src/ @@ -28,26 +28,27 @@ jobs: push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:dev platforms: linux/amd64,linux/arm64 - - push-cli-docker-image: - runs-on: ubuntu-latest - steps: - - name: Branch Checkout - uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push + - name: Build and push offat docker image uses: docker/build-push-action@v3 with: context: ./src/ - file: ./src/DockerFiles/cli-Dockerfile + file: ./src/DockerFiles/dev/cli-Dockerfile push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:dev - platforms: linux/amd64,linux/arm64 \ No newline at end of file + platforms: linux/amd64,linux/arm64 + - name: Build and push offat-api docker image + uses: docker/build-push-action@v3 + with: + context: ./src/ + file: ./src/DockerFiles/dev/backend-api-Dockerfile + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api:dev + platforms: linux/amd64,linux/arm64 + - name: Build and push offat-api-worker docker image + uses: docker/build-push-action@v3 + with: + context: ./src/ + file: ./src/DockerFiles/dev/backend-api-worker-Dockerfile + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api-worker:dev + platforms: linux/amd64,linux/arm64 diff --git a/src/DockerFiles/dev/backend-api-Dockerfile b/src/DockerFiles/dev/backend-api-Dockerfile new file mode 100644 index 0000000..a48ae00 --- /dev/null +++ b/src/DockerFiles/dev/backend-api-Dockerfile @@ -0,0 +1,5 @@ +FROM dmdhrumilmistry/offat-base:dev + +EXPOSE 8000 + +ENTRYPOINT [ "python", "-m", "offat.api" ] diff --git a/src/DockerFiles/dev/backend-api-worker-Dockerfile b/src/DockerFiles/dev/backend-api-worker-Dockerfile new file mode 100644 index 0000000..1c50659 --- /dev/null +++ b/src/DockerFiles/dev/backend-api-worker-Dockerfile @@ -0,0 +1,5 @@ +FROM dmdhrumilmistry/offat-base:dev + +WORKDIR /offat + +ENTRYPOINT [ "rq", "worker", "offat_task_queue" ] diff --git a/src/DockerFiles/dev/cli-Dockerfile b/src/DockerFiles/dev/cli-Dockerfile new file mode 100644 index 0000000..ab0e0dc --- /dev/null +++ b/src/DockerFiles/dev/cli-Dockerfile @@ -0,0 +1,3 @@ +FROM dmdhrumilmistry/offat-base:dev + +ENTRYPOINT [ "offat" ] diff --git a/src/DockerFiles/backend-api-Dockerfile b/src/DockerFiles/main/backend-api-Dockerfile similarity index 100% rename from src/DockerFiles/backend-api-Dockerfile rename to src/DockerFiles/main/backend-api-Dockerfile diff --git a/src/DockerFiles/backend-api-worker-Dockerfile b/src/DockerFiles/main/backend-api-worker-Dockerfile similarity index 100% rename from src/DockerFiles/backend-api-worker-Dockerfile rename to src/DockerFiles/main/backend-api-worker-Dockerfile diff --git a/src/DockerFiles/cli-Dockerfile b/src/DockerFiles/main/cli-Dockerfile similarity index 100% rename from src/DockerFiles/cli-Dockerfile rename to src/DockerFiles/main/cli-Dockerfile From 85c7817d1981d824d89ff373d773160393bfad5f Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 19:11:32 +0530 Subject: [PATCH 14/15] fix workflow files --- .github/workflows/dev-push.yml | 2 +- .github/workflows/pypi-publish.yml | 7 ++-- .github/workflows/release-push.yml | 56 ++++++++++++++++-------------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/.github/workflows/dev-push.yml b/.github/workflows/dev-push.yml index 6a2d4e7..793313a 100644 --- a/.github/workflows/dev-push.yml +++ b/.github/workflows/dev-push.yml @@ -1,4 +1,4 @@ -name: "Dev Release: Build and Push Docker Image to DockerHub" +name: "Dev Release: Build and Push OWASP OFFAT Docker Images to DockerHub" on: push: diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index ac0f76b..de5bd74 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -33,9 +33,12 @@ jobs: python -m pip install --upgrade pip pip install build - name: Build package - run: python -m build + run: | + cd src + python -m build - name: Publish package uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 with: user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: src/dist \ No newline at end of file diff --git a/.github/workflows/release-push.yml b/.github/workflows/release-push.yml index 748004f..7d24145 100644 --- a/.github/workflows/release-push.yml +++ b/.github/workflows/release-push.yml @@ -1,14 +1,16 @@ -name: "Build and Push Docker Image to DockerHub" +name: "Release: Build and Push OWASP OFFAT Docker Images to DockerHub" on: - release: - types: [published] - + push: + branches: + - "dev" jobs: - push-base-docker-image: + build-and-push-main-docker-images: runs-on: ubuntu-latest steps: + - name: Branch Checkout + uses: actions/checkout@v2 - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx @@ -18,33 +20,35 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: change cwd to src directory - run: cd src - - name: Build and push + - name: Build and push offat-base docker image uses: docker/build-push-action@v3 with: - platforms: linux/amd64,linux/arm64 + context: ./src/ + file: ./src/DockerFiles/base-Dockerfile push: true - file: ./Dockerfiles/base-Dockerfile tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-base:latest - - - push-cli-docker-image: - runs-on: ubuntu-latest - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to Docker Hub - uses: docker/login-action@v2 + platforms: linux/amd64,linux/arm64 + - name: Build and push offat docker image + uses: docker/build-push-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push + context: ./src/ + file: ./src/DockerFiles/main/cli-Dockerfile + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:latest + platforms: linux/amd64,linux/arm64 + - name: Build and push offat-api docker image uses: docker/build-push-action@v3 with: + context: ./src/ + file: ./src/DockerFiles/main/backend-api-Dockerfile + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api:latest platforms: linux/amd64,linux/arm64 + - name: Build and push offat-api-worker docker image + uses: docker/build-push-action@v3 + with: + context: ./src/ + file: ./src/DockerFiles/main/backend-api-worker-Dockerfile push: true - file: ./src/Dockerfiles/cli-Dockerfile - tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat:latest \ No newline at end of file + tags: ${{ secrets.DOCKERHUB_USERNAME }}/offat-api-worker:latest + platforms: linux/amd64,linux/arm64 From 31d8940720677f8768c9cc46701db3123ff0c3f1 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Sat, 28 Oct 2023 19:14:51 +0530 Subject: [PATCH 15/15] change release push workflow triggering event --- .github/workflows/release-push.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-push.yml b/.github/workflows/release-push.yml index 7d24145..9f8d1a7 100644 --- a/.github/workflows/release-push.yml +++ b/.github/workflows/release-push.yml @@ -1,9 +1,8 @@ name: "Release: Build and Push OWASP OFFAT Docker Images to DockerHub" on: - push: - branches: - - "dev" + release: + types: [published] jobs: build-and-push-main-docker-images: