From d00640c2b42a7b10f26ae61f31afebfc0a828b7d Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 17 Sep 2024 14:24:46 +0000 Subject: [PATCH 01/35] add required packages --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 8ecbb77..6ab70d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,5 @@ aio-pika dataclasses-json markdown aenum +PyJWT +cryptography From bd5850ed61a13689d2c236a663b6fa6d097fb09d Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 17 Sep 2024 23:09:58 +0000 Subject: [PATCH 02/35] ghe config --- config/__init__.py | 99 +++++++++++++++++++++++++++------------ orchestration/__init__.py | 3 +- orchestration/gh.py | 42 +++++++++++++++++ scm_services/__init__.py | 49 +++++++++++++++++-- scm_services/gh.py | 5 ++ wsgi.py | 23 ++++++++- 6 files changed, 185 insertions(+), 36 deletions(-) create mode 100644 orchestration/gh.py create mode 100644 scm_services/gh.py diff --git a/config/__init__.py b/config/__init__.py index f8aac7c..7bf2d61 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -6,9 +6,11 @@ from scm_services import \ bitbucketdc_cloner_factory, \ adoe_cloner_factory, \ + gh_cloner_factory, \ adoe_api_auth_factory, \ bbdc_api_auth_factory, \ - SCMService, ADOEService, BBDCService + github_api_auth_factory, \ + SCMService, ADOEService, BBDCService, GHService from api_utils import APISession from cxone_service import CxOneService from password_strength import PasswordPolicy @@ -18,6 +20,7 @@ from workflows import ResultSeverity, ResultStates from typing import Tuple, List from multiprocessing import cpu_count +from typing import Dict, List def get_workers_count(): @@ -64,10 +67,23 @@ def missing_at_least_one_key_path(key_path, keys): @staticmethod def mutually_exclusive(key_path, keys): - return ConfigurationException(f"Only one of these keys should be defined: {["/".join([key_path, x]) for x in keys]}") + report_list = [] + for k in keys: + if isinstance(k, str): + report_list.append("/".join([key_path, k])) + + if isinstance(k, Tuple) or isinstance(k, List): + report_list.append(f"{key_path}/({",".join(k)})") + + + return ConfigurationException(f"Only one should be defined: {report_list}") + + @staticmethod + def key_mismatch(key_path, provided, needed): + return ConfigurationException(f"{key_path} invalid: Needed {needed} but provided {provided}.") @staticmethod - def invalid_keys(key_path, keys): + def invalid_keys(key_path, keys : List): return ConfigurationException(f"These keys are invalid: {["/".join([key_path, x]) for x in keys]}") class RouteNotFoundException(Exception): @@ -195,7 +211,7 @@ def __get_secret_from_value_of_key_or_default(config_dict, key, default): return SecretRegistry.register(default) else: with open(Path(CxOneFlowConfig.__secret_root) / Path(config_dict[key]), "rt") as secret: - return SecretRegistry.register(secret.readline().strip()) + return SecretRegistry.register(secret.read().strip()) @staticmethod def __get_secret_from_value_of_key_or_fail(config_path, key, config_dict): @@ -330,45 +346,62 @@ def __cxone_client_factory(config_path, **kwargs): __ordered_scm_config_tuples = {} __scm_config_tuples_by_service_moniker = {} - __minimum_api_auth_keys = ['token', 'password'] - __basic_auth_keys = ['username', 'password'] - __all_possible_api_auth_keys = list(set(__minimum_api_auth_keys + __basic_auth_keys)) + __oauth_auth_keys = [tuple(sorted(('oauth-secret', 'oauth-id')))] + __basic_auth_keys = [tuple(sorted(('username', 'password')))] + __token_auth_keys = [('token',)] + __all_possible_api_auth_keys = list(set(__token_auth_keys + __basic_auth_keys + __oauth_auth_keys)) - __minimum_clone_auth_keys = __minimum_api_auth_keys + ['ssh'] - __all_possible_clone_auth_keys = list(set(__minimum_clone_auth_keys + __basic_auth_keys + ['ssh-port'])) + __minimum_clone_auth_keys = __all_possible_api_auth_keys + [('ssh',)] + __all_possible_clone_auth_keys = list(set(__minimum_clone_auth_keys + [('ssh-port',)])) @staticmethod def __scm_api_auth_factory(api_auth_factory, config_dict, config_path): + if config_dict is not None and len(config_dict.keys()) > 0: + + CxOneFlowConfig.__validate_auth_keys(config_dict, CxOneFlowConfig.__all_possible_api_auth_keys, config_path) - CxOneFlowConfig.__validate_no_extra_auth_keys(config_dict, CxOneFlowConfig.__all_possible_api_auth_keys, config_path) - - if len(CxOneFlowConfig.__validate_minimum_auth_keys(config_dict, CxOneFlowConfig.__minimum_api_auth_keys, config_path)) > 0: return api_auth_factory(CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "username", None), CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "password", None), - CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "token", None)) + CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "token", None), + CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "oauth-secret", None), + CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "oauth-id", None)) raise ConfigurationException(f"{config_path} SCM API authorization configuration is invalid!") @staticmethod - def __validate_minimum_auth_keys(config_dict, valid_keys, config_path): - auth_type_keys = [x for x in config_dict.keys() if x in valid_keys] - if len(auth_type_keys) > 1: - raise ConfigurationException.mutually_exclusive(config_path, auth_type_keys) - return auth_type_keys - + def __validate_auth_keys(config_dict : Dict, valid_keys : List, config_path : str): + found_count = {} + + for k in config_dict.keys(): + found = False + for valid_tuple in valid_keys: + if k in valid_tuple: + if valid_tuple not in found_count.keys(): + found_count[valid_tuple] = 1 + else: + found_count[valid_tuple] += 1 + found = True + break + if not found: + raise ConfigurationException.invalid_keys(config_path, [k]) + + # Check that only one set of keys was provided. + if len(found_count.keys()) > 1: + raise ConfigurationException.mutually_exclusive(config_path, found_count.keys()) + + # Check for missing keys in found sets. + for fk in found_count.keys(): + if found_count[fk] != len(fk): + raise ConfigurationException.missing_keys(config_path, fk) + - @staticmethod - def __validate_no_extra_auth_keys(config_dict, valid_keys, config_path): - extra_passed_keys = config_dict.keys() - valid_keys - if len(extra_passed_keys) > 0: - raise ConfigurationException.invalid_keys(config_path, extra_passed_keys) @staticmethod def __cloner_factory(scm_cloner_factory, clone_auth_dict, config_path): - CxOneFlowConfig.__validate_no_extra_auth_keys(clone_auth_dict, CxOneFlowConfig.__all_possible_clone_auth_keys, config_path) + CxOneFlowConfig.__validate_auth_keys(clone_auth_dict, CxOneFlowConfig.__all_possible_clone_auth_keys, config_path) ssh_secret = CxOneFlowConfig.__get_value_for_key_or_default('ssh', clone_auth_dict, None) if ssh_secret is not None: @@ -378,7 +411,9 @@ def __cloner_factory(scm_cloner_factory, clone_auth_dict, config_path): CxOneFlowConfig.__get_secret_from_value_of_key_or_default(clone_auth_dict, 'password', None), CxOneFlowConfig.__get_secret_from_value_of_key_or_default(clone_auth_dict, 'token', None), ssh_secret, - CxOneFlowConfig.__get_value_for_key_or_default('ssh-port', clone_auth_dict, None)) + CxOneFlowConfig.__get_value_for_key_or_default('ssh-port', clone_auth_dict, None), + CxOneFlowConfig.__get_value_for_key_or_default('oauth-secret', clone_auth_dict, None), + CxOneFlowConfig.__get_value_for_key_or_default('oauth-id', clone_auth_dict, None)) if retval is None: raise ConfigurationException(f"{config_path} SCM clone authorization configuration is invalid!") @@ -436,15 +471,21 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config __cloner_factories = { 'bbdc' : bitbucketdc_cloner_factory, - 'adoe' : adoe_cloner_factory } + 'adoe' : adoe_cloner_factory, + 'gh' : gh_cloner_factory + } __auth_factories = { 'bbdc' : bbdc_api_auth_factory, - 'adoe' : adoe_api_auth_factory } + 'adoe' : adoe_api_auth_factory, + 'gh' : github_api_auth_factory + } __scm_factories = { 'bbdc' : BBDCService, - 'adoe' : ADOEService } + 'adoe' : ADOEService, + 'gh' : BBDCService + } diff --git a/orchestration/__init__.py b/orchestration/__init__.py index f0431c3..2d6c950 100644 --- a/orchestration/__init__.py +++ b/orchestration/__init__.py @@ -1,6 +1,7 @@ from api_utils import verify_signature from .bbdc import BitBucketDataCenterOrchestrator from .adoe import AzureDevOpsEnterpriseOrchestrator +from .gh import GithubOrchestrator import logging from config import CxOneFlowConfig @@ -25,7 +26,7 @@ async def execute(orchestrator): if await orchestrator.is_signature_valid(scm_service.shared_secret): return await orchestrator.execute(cxone_service, scm_service, workflow_service) else: - OrchestrationDispatch.log().warn(f"Payload signature validation failed, webhook payload ignored.") + OrchestrationDispatch.log().warning(f"Payload signature validation failed, webhook payload ignored.") diff --git a/orchestration/gh.py b/orchestration/gh.py new file mode 100644 index 0000000..a91dd3f --- /dev/null +++ b/orchestration/gh.py @@ -0,0 +1,42 @@ +from .base import OrchestratorBase +from api_utils import signature +import logging + +class GithubOrchestrator(OrchestratorBase): + + + @staticmethod + def log(): + return logging.getLogger("BitBucketDataCenterOrchestrator") + + @property + def config_key(self): + return "gh" + + @property + def is_diagnostic(self) -> bool: + return self.__isdiagnostic + + + def __init__(self, headers : dict, webhook_payload : dict): + OrchestratorBase.__init__(self, headers, webhook_payload) + + self.__isdiagnostic = False + + self.__event = self.get_header_key_safe('X-GitHub-Event') + + if not self.__event is None and self.__event == "ping": + self.__isdiagnostic = True + return + + + async def is_signature_valid(self, shared_secret : str) -> bool: + sig = self.get_header_key_safe('X-Hub-Signature-256') + if sig is None: + GithubOrchestrator.log().warning("X-Hub-Signature-256 header is missing, rejecting.") + return False + + hashalg,hash = sig.split("=") + payload_hash = signature.get(hashalg, shared_secret, self._webhook_payload) + + return hash == payload_hash diff --git a/scm_services/__init__.py b/scm_services/__init__.py index f3e8fa6..5136749 100644 --- a/scm_services/__init__.py +++ b/scm_services/__init__.py @@ -3,10 +3,17 @@ from .scm import SCMService from .adoe import ADOEService from .bbdc import BBDCService +from .gh import GHService from api_utils import auth_basic, auth_bearer +class ScmCloneAuthSupportException(Exception): + pass + + +def bitbucketdc_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None, oauth_secret : str=None, oauth_id : str=None) -> Cloner: + if oauth_secret is not None or oauth_id is not None: + raise ScmCloneAuthSupportException("OAuth clone authentication not supported for BBDC") -def bitbucketdc_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None) -> Cloner: if username is not None and password is not None: return Cloner.using_basic_auth(username, password, True) @@ -18,7 +25,10 @@ def bitbucketdc_cloner_factory(username=None, password=None, token=None, ssh_pat return None -def adoe_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None) -> Cloner: +def adoe_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None, oauth_secret : str=None, oauth_id : str=None) -> Cloner: + if oauth_secret is not None or oauth_id is not None: + raise ScmCloneAuthSupportException("OAuth clone authentication not supported for ADO") + if username is not None and password is not None: return Cloner.using_basic_auth(username, password, True) @@ -29,15 +39,44 @@ def adoe_cloner_factory(username=None, password=None, token=None, ssh_path=None, return Cloner.using_ssh_auth(ssh_path, ssh_port) -def adoe_api_auth_factory(username=None, password=None, token=None) -> AuthBase: +def gh_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None, oauth_secret : str=None, oauth_id : str=None) -> Cloner: + return Cloner() + + +class ScmApiAuthSupportException(Exception): + pass + + +def adoe_api_auth_factory(username : str=None, password : str=None, token : str=None, oauth_secret : str=None, oauth_id : str=None) -> AuthBase: + if oauth_secret is not None or oauth_id is not None: + raise ScmApiAuthSupportException("OAuth API authentication not supported for ADO") + if token is not None: return auth_basic("", token) - else: + elif username is not None and password is not None: return auth_basic(username, password) + else: + raise ScmApiAuthSupportException("Unable to determine API auth method.") + +def bbdc_api_auth_factory(username=None, password=None, token=None, oauth_secret : str=None, oauth_id : str=None) -> AuthBase: + if oauth_secret is not None or oauth_id is not None: + raise ScmApiAuthSupportException("OAuth API authentication not supported for BBDC") -def bbdc_api_auth_factory(username=None, password=None, token=None) -> AuthBase: if token is not None: return auth_bearer(token) + elif username is not None and password is not None: + return auth_basic(username, password) else: + raise ScmApiAuthSupportException("Unable to determine API auth method.") + +def github_api_auth_factory(username=None, password=None, token=None, oauth_secret : str=None, oauth_id : str=None) -> AuthBase: + if token is not None: + return auth_bearer(token) + elif username is not None and password is not None: return auth_basic(username, password) + elif oauth_id is not None and oauth_secret is not None: + pass + else: + raise ScmApiAuthSupportException("Unable to determine API auth method.") + diff --git a/scm_services/gh.py b/scm_services/gh.py new file mode 100644 index 0000000..06444aa --- /dev/null +++ b/scm_services/gh.py @@ -0,0 +1,5 @@ +from .scm import SCMService + + +class GHService(SCMService): + pass diff --git a/wsgi.py b/wsgi.py index 6703975..c4cdff9 100644 --- a/wsgi.py +++ b/wsgi.py @@ -6,7 +6,8 @@ """ from _agent import __agent__ from flask import Flask, request, Response, send_from_directory -from orchestration import OrchestrationDispatch, BitBucketDataCenterOrchestrator, AzureDevOpsEnterpriseOrchestrator +from orchestration import OrchestrationDispatch, BitBucketDataCenterOrchestrator, \ + AzureDevOpsEnterpriseOrchestrator, GithubOrchestrator import json, logging, asyncio, os from config import CxOneFlowConfig, ConfigurationException, get_config_path from time import perf_counter_ns @@ -49,6 +50,26 @@ async def bbdc_webhook_endpoint(): __log.exception(ex) return Response(status=400) +@app.post("/gh") +async def github_webhook_endpoint(): + __log.info("Received hook for Github") + __log.debug(f"github webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") + try: + orch = GithubOrchestrator(request.headers, request.data) + + if not orch.is_diagnostic: + TaskManager.in_background(OrchestrationDispatch.execute(orch)) + return Response(status=204) + else: + # The ping has no route URL associated, so check if any route matches. + for service in CxOneFlowConfig.retrieve_scm_services(orch.config_key): + if await orch.is_signature_valid(service.shared_secret): + return Response(status=200) + return Response(status=401) + + except Exception as ex: + __log.exception(ex) + return Response(status=400) @app.post("/adoe") async def adoe_webhook_endpoint(): From 86dec1cb4be74f56d49cb305d2d42c222ca38914 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Wed, 18 Sep 2024 20:42:38 +0000 Subject: [PATCH 03/35] ghe wip --- api_utils/__init__.py | 1 + api_utils/gh_oauth.py | 17 ++++ config/__init__.py | 17 ++-- orchestration/adoe.py | 14 ++-- orchestration/base.py | 3 + orchestration/gh.py | 173 ++++++++++++++++++++++++++++++++++++++- scm_services/__init__.py | 4 +- 7 files changed, 211 insertions(+), 18 deletions(-) create mode 100644 api_utils/gh_oauth.py diff --git a/api_utils/__init__.py b/api_utils/__init__.py index b2967be..d2c887f 100644 --- a/api_utils/__init__.py +++ b/api_utils/__init__.py @@ -1,6 +1,7 @@ from .apisession import APISession from requests.auth import AuthBase,HTTPBasicAuth from .signatures import signature +from .gh_oauth import GithubOauth def auth_basic(username, password) -> AuthBase: diff --git a/api_utils/gh_oauth.py b/api_utils/gh_oauth.py new file mode 100644 index 0000000..20f3d67 --- /dev/null +++ b/api_utils/gh_oauth.py @@ -0,0 +1,17 @@ +from requests.auth import AuthBase +import logging + + +class GithubOauth(AuthBase): + + @staticmethod + def log(): + return logging.getLogger("GithubOauth") + + def __init__(self, id, secret): + pass + + def __call__(self, r): + GithubOauth.log().debug(type(r)) + return r + diff --git a/config/__init__.py b/config/__init__.py index 7bf2d61..c26fb30 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -203,15 +203,20 @@ def __get_value_for_key_or_fail(config_path, key, config_dict): return config_dict[key] @staticmethod - def __get_secret_from_value_of_key_or_default(config_dict, key, default): + def __get_file_contents_from_value_of_key_or_default(config_dict, key, default): if not key in config_dict.keys(): - return SecretRegistry.register(default) + return default else: if not os.path.isfile(Path(CxOneFlowConfig.__secret_root) / Path(config_dict[key])): - return SecretRegistry.register(default) + return default else: - with open(Path(CxOneFlowConfig.__secret_root) / Path(config_dict[key]), "rt") as secret: - return SecretRegistry.register(secret.read().strip()) + with open(Path(CxOneFlowConfig.__secret_root) / Path(config_dict[key]), "rt") as f: + return f.read().strip() + + + @staticmethod + def __get_secret_from_value_of_key_or_default(config_dict, key, default): + return SecretRegistry.register(CxOneFlowConfig.__get_file_contents_from_value_of_key_or_default(config_dict, key, default)) @staticmethod def __get_secret_from_value_of_key_or_fail(config_path, key, config_dict): @@ -364,7 +369,7 @@ def __scm_api_auth_factory(api_auth_factory, config_dict, config_path): CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "password", None), CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "token", None), CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "oauth-secret", None), - CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "oauth-id", None)) + CxOneFlowConfig.__get_file_contents_from_value_of_key_or_default(config_dict, "oauth-id", None)) raise ConfigurationException(f"{config_path} SCM API authorization configuration is invalid!") diff --git a/orchestration/adoe.py b/orchestration/adoe.py index 21091e0..0e3b724 100644 --- a/orchestration/adoe.py +++ b/orchestration/adoe.py @@ -56,7 +56,7 @@ def __init__(self, headers, webhook_payload): self.__event = [x.value for x in list(self.__payload_type_query.find(self.__json))][0] self.__route_urls = [x.value for x in list(self.__remoteurl_query.find(self.__json))] self.__clone_url = self.__route_urls[0] - self.__default_branches = [AzureDevOpsEnterpriseOrchestrator.__normalize_branch_name(x.value) for x in list(self.__push_default_branch_query.find(self.__json))] + self.__default_branches = [OrchestratorBase.normalize_branch_name(x.value) for x in list(self.__push_default_branch_query.find(self.__json))] self.__repo_key = [x.value for x in list(self.__repo_project_key_query.find(self.__json))][0] self.__repo_slug = [x.value for x in list(self.__repo_slug_query.find(self.__json))][0] self.__collection_url = [x.value for x in list(self.__collection_url_query.find(self.__json))][0] @@ -66,10 +66,6 @@ def __init__(self, headers, webhook_payload): async def execute(self, cxone_service: CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): return await AzureDevOpsEnterpriseOrchestrator.__workflow_map[self.__event](self, cxone_service, scm_service, workflow_service) - @staticmethod - def __normalize_branch_name(branch): - return branch.split("/")[-1:].pop() - @property def is_diagnostic(self): return self.__isdiagnostic @@ -139,7 +135,7 @@ async def __is_pr_draft(self) -> bool: async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): - self.__source_branch = self.__target_branch = AzureDevOpsEnterpriseOrchestrator.__normalize_branch_name( + self.__source_branch = self.__target_branch = OrchestratorBase.normalize_branch_name( [x.value for x in list(self.__push_target_branch_query.find(self.__json))][0]) self.__source_hash = self.__target_hash = [x.value for x in list(self.__push_target_hash_query.find(self.__json))][0] @@ -150,8 +146,8 @@ async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_serv AzureDevOpsEnterpriseOrchestrator.log().info(f"Skipping draft PR {AzureDevOpsEnterpriseOrchestrator.__pr_self_link_query.find(self.__json)[0].value}") return - self.__source_branch = AzureDevOpsEnterpriseOrchestrator.__normalize_branch_name([x.value for x in list(self.__pr_frombranch_query.find(self.__json))][0]) - self.__target_branch = AzureDevOpsEnterpriseOrchestrator.__normalize_branch_name([x.value for x in list(self.__pr_tobranch_query.find(self.__json))][0]) + self.__source_branch = OrchestratorBase.normalize_branch_name([x.value for x in list(self.__pr_frombranch_query.find(self.__json))][0]) + self.__target_branch = OrchestratorBase.normalize_branch_name([x.value for x in list(self.__pr_tobranch_query.find(self.__json))][0]) self.__source_hash = [x.value for x in list(self.__pr_fromhash_query.find(self.__json))][0] self.__target_hash = [x.value for x in list(self.__pr_tohash_query.find(self.__json))][0] self.__pr_id = str([x.value for x in list(self.__pr_id_query.find(self.__json))][0]) @@ -177,7 +173,7 @@ async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_serv AzureDevOpsEnterpriseOrchestrator.log().error(f"Response [{repo_details.status_code}] to request for repository details, event handling aborted.") return - self.__default_branches = [AzureDevOpsEnterpriseOrchestrator.__normalize_branch_name(repo_details.json()['defaultBranch'])] + self.__default_branches = [OrchestratorBase.normalize_branch_name(repo_details.json()['defaultBranch'])] return await OrchestratorBase._execute_pr_scan_workflow(self, cxone_service, scm_service, workflow_service) diff --git a/orchestration/base.py b/orchestration/base.py index 6b4fc24..a8abf7a 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -11,6 +11,9 @@ class OrchestratorBase: + @staticmethod + def normalize_branch_name(branch): + return branch.split("/")[-1:].pop() @staticmethod def log() -> logging.Logger: diff --git a/orchestration/gh.py b/orchestration/gh.py index a91dd3f..6727569 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -1,9 +1,43 @@ from .base import OrchestratorBase from api_utils import signature -import logging +from jsonpath_ng import parse +import logging, json +from cxone_service import CxOneService +from scm_services import SCMService, Cloner +from workflows.state_service import WorkflowStateService class GithubOrchestrator(OrchestratorBase): + __install_action_query = parse("$.action") + __install_sender_query = parse("$.sender.login") + __install_target_query = parse("$.installation.account.login") + __install_target_type_query = parse("$.installation.account.type") + __install_repo_selection_query = parse("$.installation.repository_selection") + __install_events_query = parse("$.installation.events") + __install_permissions_query = parse("$.installation.permissions") + __install_route_url_query = parse("$.installation.account.html_url") + + __push_route_url_query = parse("$.repository[clone_url,ssh_url]") + __push_ssh_clone_url_query = parse("$.repository.ssh_url") + __push_http_clone_url_query = parse("$.repository.clone_url") + __push_target_branch_query = parse("$.ref") + __push_target_hash_query = parse("$.head_commit.id") + __push_project_key_query = parse("$.repository.name") + __push_org_key_query = parse("$.repository.owner.name") + + __expected_events = ['pull_request', 'pull_request_review', 'push'] + __expected_permissions = { + "contents" : "read", + "metadata" : "read", + "pull_requests" : "write" + } + + __permission_weights = { + "read" : 1, + "write" : 2 + } + + @staticmethod def log(): @@ -17,6 +51,70 @@ def config_key(self): def is_diagnostic(self) -> bool: return self.__isdiagnostic + async def __log_app_install(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): + action = GithubOrchestrator.__install_action_query.find(self.__json)[0].value + sender = GithubOrchestrator.__install_sender_query.find(self.__json)[0].value + target = GithubOrchestrator.__install_target_query.find(self.__json)[0].value + target_type = GithubOrchestrator.__install_target_type_query.find(self.__json)[0].value + + GithubOrchestrator.log().info(f"Install event '{action}': Initiated by [{sender}] on {target_type} [{target}]") + if action in ["created", "new_permissions_accepted", "added"]: + warned = False + bad = False + if not target_type == "Organization": + GithubOrchestrator.log().warning(f"Install target '{target}' is type '{target_type}' but expected to be 'Organization'.") + warned = True + + repo_selection = GithubOrchestrator.__install_repo_selection_query.find(self.__json)[0].value + if not repo_selection == "all": + GithubOrchestrator.log().warning(f"Repository selection is '{repo_selection}' but expected to be 'all'.") + warned = True + + events = GithubOrchestrator.__install_events_query.find(self.__json) + if len(events) == 0: + unhandled_events = GithubOrchestrator.__expected_events + else: + unhandled_events = [x for x in GithubOrchestrator.__expected_events if x not in events[0].value] + if len(unhandled_events) > 0: + bad = True + GithubOrchestrator.log().error(f"Event types [{",".join(unhandled_events)}] do not emit web hook events as expected.") + + + permissions_not_found = dict(GithubOrchestrator.__expected_permissions) + payload_permissions = GithubOrchestrator.__install_permissions_query.find(self.__json) + if len(payload_permissions) > 0: + permissions = payload_permissions[0].value + for p in permissions.keys(): + if p in permissions_not_found.keys() and GithubOrchestrator.__permission_weights[permissions_not_found[p]] <= \ + GithubOrchestrator.__permission_weights[permissions[p]]: + permissions_not_found.pop(p) + + if len(permissions_not_found) > 0: + bad = True + GithubOrchestrator.log().error(f"Missing expected permissions {permissions_not_found}.") + + if warned: + GithubOrchestrator.log().warning("Events will still be handled but the scope of repositories covered may not be as expected.") + + if bad: + GithubOrchestrator.log().error("The GitHub app may be misconfigured, workflows will likely not work as expected.") + + if not bad and not warned: + GithubOrchestrator.log().info("The GitHub app appears to be properly configured.") + + + + def __installation_route_urls(self): + return [GithubOrchestrator.__install_route_url_query.find(self.__json)[0].value] + + def __push_route_urls(self): + return [GithubOrchestrator.__push_route_url_query.find(self.__json)[0].value] + + def __push_clone_urls(self): + return { + "ssh" : GithubOrchestrator.__push_ssh_clone_url_query.find(self.__json)[0].value, + "http" : GithubOrchestrator.__push_http_clone_url_query.find(self.__json)[0].value + } def __init__(self, headers : dict, webhook_payload : dict): OrchestratorBase.__init__(self, headers, webhook_payload) @@ -28,7 +126,33 @@ def __init__(self, headers : dict, webhook_payload : dict): if not self.__event is None and self.__event == "ping": self.__isdiagnostic = True return + + self.__json = json.loads(webhook_payload) + + self.__route_urls = GithubOrchestrator.__route_url_parser_dispatch_map[self.__event](self) \ + if self.__event in GithubOrchestrator.__route_url_parser_dispatch_map.keys() else [] + + self.__clone_urls = GithubOrchestrator.__clone_url_parser_dispatch_map[self.__event](self) \ + if self.__event in GithubOrchestrator.__clone_url_parser_dispatch_map.keys() else {} + + + async def execute(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): + if self.__event not in GithubOrchestrator.__workflow_map.keys(): + GithubOrchestrator.log().error(f"Unhandled event type: {self.__event}") + else: + return await GithubOrchestrator.__workflow_map[self.__event](self, cxone_service, scm_service, workflow_service) + + + async def _get_target_branch_and_hash(self) -> tuple: + return self.__target_branch, self.__target_hash + async def _get_source_branch_and_hash(self) -> tuple: + return self.__source_branch, self.__source_hash + + + @property + def route_urls(self) -> list: + return self.__route_urls async def is_signature_valid(self, shared_secret : str) -> bool: sig = self.get_header_key_safe('X-Hub-Signature-256') @@ -40,3 +164,50 @@ async def is_signature_valid(self, shared_secret : str) -> bool: payload_hash = signature.get(hashalg, shared_secret, self._webhook_payload) return hash == payload_hash + + + async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): + self.__target_branch = self.__source_branch = OrchestratorBase.normalize_branch_name( + GithubOrchestrator.__push_target_branch_query.find(self.__json)[0].value) + self.__target_hash = self.__source_hash = GithubOrchestrator.__push_target_hash_query.find(self.__json)[0].value + + return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) + + async def _get_protected_branches(self, scm_service : SCMService) -> list: + raise NotImplementedError("_get_protected_branches") + + + @property + def _repo_project_key(self) -> str: + return GithubOrchestrator.__push_project_key_query.find(self.__json)[0].value + + @property + def _repo_organization(self) -> str: + return GithubOrchestrator.__push_org_key_query.find(self.__json)[0].value + + @property + def _repo_slug(self) -> str: + return self._repo_project_key + + @property + def _repo_name(self) -> str: + return self._repo_project_key + + def _repo_clone_url(self, cloner) -> str: + return self.__clone_urls[cloner.select_protocol_from_supported(self.__clone_urls.keys())] + + __workflow_map = { + "installation" : __log_app_install, + "installation_repositories" : __log_app_install, + "push" : _execute_push_scan_workflow + } + + __route_url_parser_dispatch_map = { + "installation" : __installation_route_urls, + "installation_repositories" : __installation_route_urls, + "push" : __push_route_urls + } + + __clone_url_parser_dispatch_map = { + "push" : __push_clone_urls + } diff --git a/scm_services/__init__.py b/scm_services/__init__.py index 5136749..c80af5e 100644 --- a/scm_services/__init__.py +++ b/scm_services/__init__.py @@ -4,7 +4,7 @@ from .adoe import ADOEService from .bbdc import BBDCService from .gh import GHService -from api_utils import auth_basic, auth_bearer +from api_utils import auth_basic, auth_bearer, GithubOauth class ScmCloneAuthSupportException(Exception): pass @@ -76,7 +76,7 @@ def github_api_auth_factory(username=None, password=None, token=None, oauth_secr elif username is not None and password is not None: return auth_basic(username, password) elif oauth_id is not None and oauth_secret is not None: - pass + return GithubOauth(oauth_id, oauth_secret) else: raise ScmApiAuthSupportException("Unable to determine API auth method.") From fcf3acac325e86455518c0f9904ce6d9dac1aeb1 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Thu, 19 Sep 2024 17:44:04 +0000 Subject: [PATCH 04/35] add debug info --- orchestration/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/orchestration/base.py b/orchestration/base.py index a8abf7a..5555dcc 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -131,6 +131,7 @@ async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_se async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService) -> ScanInspector: + OrchestratorBase.log().debug("_execute_pr_scan_workflow") source_branch, source_hash = await self._get_source_branch_and_hash() target_branch, _ = await self._get_target_branch_and_hash() From 12193a3616635dad8fe74e4a6ef8315954a048b1 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Thu, 19 Sep 2024 18:45:23 +0000 Subject: [PATCH 05/35] implementing differently --- api_utils/gh_oauth.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 api_utils/gh_oauth.py diff --git a/api_utils/gh_oauth.py b/api_utils/gh_oauth.py deleted file mode 100644 index 20f3d67..0000000 --- a/api_utils/gh_oauth.py +++ /dev/null @@ -1,17 +0,0 @@ -from requests.auth import AuthBase -import logging - - -class GithubOauth(AuthBase): - - @staticmethod - def log(): - return logging.getLogger("GithubOauth") - - def __init__(self, id, secret): - pass - - def __call__(self, r): - GithubOauth.log().debug(type(r)) - return r - From a8cac5008ddc2590c2b45e3d72a6d7c7c1df294c Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 20 Sep 2024 02:11:45 +0000 Subject: [PATCH 06/35] support auth changes based on event --- api_utils/__init__.py | 23 ++--- api_utils/apisession.py | 22 ++-- api_utils/auth_factories.py | 92 +++++++++++++++++ api_utils/bearer.py | 10 ++ config/__init__.py | 195 +++++++++++++++++++++--------------- orchestration/gh.py | 20 +++- scm_services/__init__.py | 75 -------------- scm_services/adoe.py | 3 +- scm_services/bbdc.py | 4 +- scm_services/cloner.py | 5 +- scm_services/gh.py | 2 + scm_services/scm.py | 14 +-- 12 files changed, 274 insertions(+), 191 deletions(-) create mode 100644 api_utils/auth_factories.py create mode 100644 api_utils/bearer.py diff --git a/api_utils/__init__.py b/api_utils/__init__.py index d2c887f..3544728 100644 --- a/api_utils/__init__.py +++ b/api_utils/__init__.py @@ -1,24 +1,15 @@ -from .apisession import APISession -from requests.auth import AuthBase,HTTPBasicAuth from .signatures import signature -from .gh_oauth import GithubOauth +from .auth_factories import AuthFactory, StaticAuthFactory +from requests.auth import HTTPBasicAuth +from .bearer import HTTPBearerAuth -def auth_basic(username, password) -> AuthBase: - return HTTPBasicAuth(username, password) +def auth_basic(username, password) -> AuthFactory: + return StaticAuthFactory(HTTPBasicAuth(username, password)) -def auth_bearer(token) -> AuthBase: - class HTTPBearerAuth(AuthBase): - def __init__(self, token): - AuthBase.__init__(self) - self.__token = token - - def __call__(self, r): - r.headers["Authorization"] = f"Bearer {self.__token}" - return r - - return HTTPBearerAuth(token) +def auth_bearer(token) -> AuthFactory: + return StaticAuthFactory(HTTPBearerAuth(token)) def verify_signature(signature_header, secret, body) -> bool: (algorithm, hash) = signature_header.split("=") diff --git a/api_utils/apisession.py b/api_utils/apisession.py index 51781e9..f9a7834 100644 --- a/api_utils/apisession.py +++ b/api_utils/apisession.py @@ -1,10 +1,9 @@ from _agent import __agent__ -from requests.auth import AuthBase from requests import Response from requests import request from typing import Dict, Union, Any import urllib, logging, sys, asyncio - +from api_utils import AuthFactory class SCMAuthException(Exception): pass @@ -18,27 +17,36 @@ class APISession: def log(clazz): return logging.getLogger(clazz.__name__) - def __init__(self, api_base_endpoint : str, auth : AuthBase, timeout : int = 60, retries : int = 3, proxies : Dict = None, ssl_verify : Union[bool, str] = True): + def __init__(self, api_base_endpoint : str, api_suffix : str, auth : AuthFactory, timeout : int = 60, retries : int = 3, proxies : Dict = None, ssl_verify : Union[bool, str] = True): self.__headers = { "User-Agent" : __agent__ } self.__base_endpoint = api_base_endpoint + self.__api_suffix = api_suffix self.__timeout = timeout self.__retries = retries self.__verify = ssl_verify self.__proxies = proxies - self.__auth = auth + self.__auth_factory = auth + + @property + def _api_endpoint(self): + base = self.__base_endpoint.rstrip("/") + if self.__api_suffix is not None and len(self.__api_suffix) > 0: + base = f"{base}/{self.__api_suffix.lstrip("/").rstrip("/")}" + + return base def _form_url(self, url_path, anchor=None, **kwargs): - base = self.__base_endpoint.rstrip("/") + base = self._api_endpoint suffix = urllib.parse.quote(url_path.lstrip("/")) args = [f"{x}={urllib.parse.quote(str(kwargs[x]))}" for x in kwargs.keys()] return f"{base}/{suffix}{"?" if len(args) > 0 else ""}{"&".join(args)}{f"#{anchor}" if anchor is not None else ""}" - async def exec(self, method : str, path : str, query : Dict = None, body : Any = None, extra_headers : Dict = None) -> Response: + async def exec(self, event_msg : Dict, method : str, path : str, query : Dict = None, body : Any = None, extra_headers : Dict = None) -> Response: url = self._form_url(path) headers = dict(self.__headers) if not extra_headers is None: @@ -50,7 +58,7 @@ async def exec(self, method : str, path : str, query : Dict = None, body : Any = APISession.log().debug(f"Executing: {prepStr} #{tryCount}") response = await asyncio.to_thread(request, method=method, url=url, params=query, - data=body, headers=headers, auth=self.__auth, timeout=self.__timeout, + data=body, headers=headers, auth=await self.__auth_factory.make_auth(event_msg, self._api_endpoint), timeout=self.__timeout, proxies=self.__proxies, verify=self.__verify) logStr = f"{response.status_code}: {response.reason} {prepStr}" diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py new file mode 100644 index 0000000..3469311 --- /dev/null +++ b/api_utils/auth_factories.py @@ -0,0 +1,92 @@ +from requests.auth import AuthBase +from requests import request +from typing import Dict +from jsonpath_ng import parse +import jwt, time, asyncio, logging +from datetime import datetime +from threading import Lock +from _agent import __agent__ +from api_utils.bearer import HTTPBearerAuth +from cxone_api.util import json_on_ok +from cxoneflow_logging import SecretRegistry + +class AuthFactoryException(BaseException): + pass + +class AuthFactory: + async def make_auth(self, event_context : Dict=None, api_url : str=None) -> AuthBase: + raise NotImplementedError("make_auth") + +class StaticAuthFactory(AuthFactory): + def __init__(self, static_auth : AuthBase): + self.__auth = static_auth + + async def make_auth(self, event_context : Dict=None, api_url : str=None) -> AuthBase: + return self.__auth + + +class GithubAppAuthFactory(AuthFactory): + __lock = Lock() + + __token_cache = {} + + __event_installation_id = parse("$.installation.id") + __event_app_id = parse("$.sender.id") + + @staticmethod + def log(): + return logging.getLogger("GithubAppAuthFactory") + + def __init__(self, private_key : str): + self.__pkey = private_key + + def __encoded_jwt_factory(self, install_id : int) -> str: + payload = { + 'iat' : int(time.time()), + "exp" : int(time.time()) + 600, + 'iss' : install_id, + 'alg' : "RS256" + } + + return jwt.encode(payload, self.__pkey, algorithm='RS256') + + async def make_auth(self, event_context : Dict=None, api_url : str=None) -> AuthBase: + if event_context is None or api_url is None: + raise AuthFactoryException("Event context and API url are required.") + + install_id_found = GithubAppAuthFactory.__event_installation_id.find(event_context) + if len(install_id_found) == 0: + raise AuthFactoryException("GitHub installation id was not found in the event payload.") + install_id = install_id_found[0].value + + app_id_found = GithubAppAuthFactory.__event_app_id.find(event_context) + if len(app_id_found) == 0: + raise AuthFactoryException("GitHub app id was not found in the event payload.") + app_id = app_id_found[0].value + + token_tuple = None + + with GithubAppAuthFactory.__lock: + if install_id in GithubAppAuthFactory.__token_cache.keys(): + token_tuple = tuple(GithubAppAuthFactory.__token_cache[install_id]) + exp = token_tuple[1] + if datetime.now(exp.tzinfo) >= exp: + token_tuple = None + + if token_tuple is None: + token_response = json_on_ok(await asyncio.to_thread(request, method="POST", url=f"{api_url.rstrip("/")}/app/installations/{install_id}/access_tokens", + headers = {"User-Agent" : __agent__}, + auth=HTTPBearerAuth(self.__encoded_jwt_factory(app_id)))) + + token_tuple = (SecretRegistry.register(token_response['token']), datetime.fromisoformat(token_response['expires_at'])) + + with GithubAppAuthFactory.__lock: + GithubAppAuthFactory.__token_cache[install_id] = token_tuple + + + return HTTPBearerAuth (token_tuple[0]) + + + + + diff --git a/api_utils/bearer.py b/api_utils/bearer.py new file mode 100644 index 0000000..9635978 --- /dev/null +++ b/api_utils/bearer.py @@ -0,0 +1,10 @@ +from requests.auth import AuthBase + +class HTTPBearerAuth(AuthBase): + def __init__(self, token): + AuthBase.__init__(self) + self.__token = token + + def __call__(self, r): + r.headers["Authorization"] = f"Bearer {self.__token}" + return r diff --git a/config/__init__.py b/config/__init__.py index c26fb30..256db30 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -3,15 +3,10 @@ from pathlib import Path import re import yaml, logging, cxone_api as cx, os -from scm_services import \ - bitbucketdc_cloner_factory, \ - adoe_cloner_factory, \ - gh_cloner_factory, \ - adoe_api_auth_factory, \ - bbdc_api_auth_factory, \ - github_api_auth_factory, \ - SCMService, ADOEService, BBDCService, GHService -from api_utils import APISession +from scm_services import SCMService, ADOEService, BBDCService, GHService, Cloner +from api_utils import auth_basic, auth_bearer +from api_utils.apisession import APISession +from api_utils.auth_factories import AuthFactory, GithubAppAuthFactory from cxone_service import CxOneService from password_strength import PasswordPolicy from cxoneflow_logging import SecretRegistry @@ -351,74 +346,24 @@ def __cxone_client_factory(config_path, **kwargs): __ordered_scm_config_tuples = {} __scm_config_tuples_by_service_moniker = {} - __oauth_auth_keys = [tuple(sorted(('oauth-secret', 'oauth-id')))] - __basic_auth_keys = [tuple(sorted(('username', 'password')))] - __token_auth_keys = [('token',)] - __all_possible_api_auth_keys = list(set(__token_auth_keys + __basic_auth_keys + __oauth_auth_keys)) - - __minimum_clone_auth_keys = __all_possible_api_auth_keys + [('ssh',)] - __all_possible_clone_auth_keys = list(set(__minimum_clone_auth_keys + [('ssh-port',)])) - @staticmethod def __scm_api_auth_factory(api_auth_factory, config_dict, config_path): - if config_dict is not None and len(config_dict.keys()) > 0: + retval = None - CxOneFlowConfig.__validate_auth_keys(config_dict, CxOneFlowConfig.__all_possible_api_auth_keys, config_path) - - return api_auth_factory(CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "username", None), - CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "password", None), - CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "token", None), - CxOneFlowConfig.__get_secret_from_value_of_key_or_default(config_dict, "oauth-secret", None), - CxOneFlowConfig.__get_file_contents_from_value_of_key_or_default(config_dict, "oauth-id", None)) - - raise ConfigurationException(f"{config_path} SCM API authorization configuration is invalid!") - - @staticmethod - def __validate_auth_keys(config_dict : Dict, valid_keys : List, config_path : str): - found_count = {} - - for k in config_dict.keys(): - found = False - for valid_tuple in valid_keys: - if k in valid_tuple: - if valid_tuple not in found_count.keys(): - found_count[valid_tuple] = 1 - else: - found_count[valid_tuple] += 1 - found = True - break - if not found: - raise ConfigurationException.invalid_keys(config_path, [k]) - - # Check that only one set of keys was provided. - if len(found_count.keys()) > 1: - raise ConfigurationException.mutually_exclusive(config_path, found_count.keys()) - - # Check for missing keys in found sets. - for fk in found_count.keys(): - if found_count[fk] != len(fk): - raise ConfigurationException.missing_keys(config_path, fk) - + if config_dict is not None and len(config_dict.keys()) > 0: + retval = api_auth_factory(config_path, config_dict) + if retval is None: + raise ConfigurationException(f"{config_path} SCM API authorization configuration is invalid!") + + return retval @staticmethod def __cloner_factory(scm_cloner_factory, clone_auth_dict, config_path): - CxOneFlowConfig.__validate_auth_keys(clone_auth_dict, CxOneFlowConfig.__all_possible_clone_auth_keys, config_path) - - ssh_secret = CxOneFlowConfig.__get_value_for_key_or_default('ssh', clone_auth_dict, None) - if ssh_secret is not None: - ssh_secret = Path(CxOneFlowConfig.__secret_root) / Path(ssh_secret) - - retval = scm_cloner_factory(CxOneFlowConfig.__get_secret_from_value_of_key_or_default(clone_auth_dict, 'username', None), - CxOneFlowConfig.__get_secret_from_value_of_key_or_default(clone_auth_dict, 'password', None), - CxOneFlowConfig.__get_secret_from_value_of_key_or_default(clone_auth_dict, 'token', None), - ssh_secret, - CxOneFlowConfig.__get_value_for_key_or_default('ssh-port', clone_auth_dict, None), - CxOneFlowConfig.__get_value_for_key_or_default('oauth-secret', clone_auth_dict, None), - CxOneFlowConfig.__get_value_for_key_or_default('oauth-id', clone_auth_dict, None)) + retval = scm_cloner_factory(Path(CxOneFlowConfig.__secret_root), clone_auth_dict) if retval is None: raise ConfigurationException(f"{config_path} SCM clone authorization configuration is invalid!") @@ -451,12 +396,13 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config api_auth_dict = CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'api-auth', connection_config_dict) api_session = APISession(CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict), \ + CxOneFlowConfig.__get_value_for_key_or_default('api-url-suffix', connection_config_dict, None), \ CxOneFlowConfig.__scm_api_auth_factory(api_auth_factory, api_auth_dict, f"{config_path}/connection/api-auth"), \ CxOneFlowConfig.__get_value_for_key_or_default('timeout-seconds', connection_config_dict, 60), \ CxOneFlowConfig.__get_value_for_key_or_default('retries', connection_config_dict, 3), \ CxOneFlowConfig.__get_value_for_key_or_default('proxies', connection_config_dict, None), \ - CxOneFlowConfig.__get_value_for_key_or_default('ssl-verify', connection_config_dict, CxOneFlowConfig.get_default_ssl_verify_value()), \ - ) + CxOneFlowConfig.__get_value_for_key_or_default('ssl-verify', connection_config_dict, + CxOneFlowConfig.get_default_ssl_verify_value())) scm_shared_secret = CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(f"{config_path}/connection", 'shared-secret', connection_config_dict) secret_test_result = CxOneFlowConfig.__shared_secret_policy.test(scm_shared_secret) @@ -473,25 +419,116 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config return repo_matcher, cxone_service, scm_service, workflow_service_client + @staticmethod + def __has_basic_auth(config_dict : Dict) -> bool: + if 'username' in config_dict.keys() and 'password' in config_dict.keys(): + if config_dict['username'] is not None and config_dict['password'] is not None: + return True + return False + + @staticmethod + def __has_token_auth(config_dict : Dict) -> bool: + if 'token' in config_dict.keys(): + if config_dict['token'] is not None: + return True + return False + + @staticmethod + def __has_ssh_auth(config_dict : Dict) -> bool: + if 'ssh' in config_dict.keys(): + if config_dict['ssh'] is not None: + return True + return False + + + @staticmethod + def __bitbucketdc_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: + if CxOneFlowConfig.__has_basic_auth(config_dict): + return Cloner.using_basic_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), + CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict), True) + + if CxOneFlowConfig.__has_token_auth(config_dict): + return Cloner.using_token_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) + + if CxOneFlowConfig.__has_ssh_auth(config_dict): + return Cloner.using_ssh_auth(Path(CxOneFlowConfig.__secret_root) / + Path(CxOneFlowConfig.__get_value_for_key_or_fail(config_path, "ssh", config_dict)), + config_dict['ssh-port'] if 'ssh-port' in config_dict.keys() else None) + + return None + + @staticmethod + def __adoe_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: + if CxOneFlowConfig.__has_basic_auth(config_dict): + return Cloner.using_basic_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), + CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict), True) + + if CxOneFlowConfig.__has_token_auth(config_dict): + return Cloner.using_basic_auth("", CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict), True) + + if CxOneFlowConfig.__has_ssh_auth(config_dict): + return Cloner.using_ssh_auth(Path(CxOneFlowConfig.__secret_root) / + Path(CxOneFlowConfig.__get_value_for_key_or_fail(config_path, "ssh", config_dict)), + config_dict['ssh-port'] if 'ssh-port' in config_dict.keys() else None) + + CxOneFlowConfig.__get_value_for_key_or_fail + + return None + + @staticmethod + def __gh_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: + return Cloner() + + + @staticmethod + def __adoe_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactory: + if CxOneFlowConfig.__has_token_auth(config_dict): + return auth_basic("", CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) + elif CxOneFlowConfig.__has_basic_auth(config_dict): + return auth_basic(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), + CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict)) + + return None + + + @staticmethod + def __bbdc_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactory: + if CxOneFlowConfig.__has_token_auth(config_dict): + return auth_bearer(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) + elif CxOneFlowConfig.__has_basic_auth(config_dict): + return auth_basic(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), + CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict)) + + return None + + @staticmethod + def __github_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactory: + if CxOneFlowConfig.__has_token_auth(config_dict): + return auth_bearer(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) + elif CxOneFlowConfig.__has_basic_auth(config_dict): + return auth_basic(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), + CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict)) + elif 'app-private-key' in config_dict.keys() and config_dict['app-private-key'] is not None: + return GithubAppAuthFactory(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "app-private-key", config_dict)) + + return None + __cloner_factories = { - 'bbdc' : bitbucketdc_cloner_factory, - 'adoe' : adoe_cloner_factory, - 'gh' : gh_cloner_factory + 'bbdc' : __bitbucketdc_cloner_factory, + 'adoe' : __adoe_cloner_factory, + 'gh' : __gh_cloner_factory } __auth_factories = { - 'bbdc' : bbdc_api_auth_factory, - 'adoe' : adoe_api_auth_factory, - 'gh' : github_api_auth_factory + 'bbdc' : __bbdc_api_auth_factory, + 'adoe' : __adoe_api_auth_factory, + 'gh' : __github_api_auth_factory } __scm_factories = { 'bbdc' : BBDCService, 'adoe' : ADOEService, - 'gh' : BBDCService + 'gh' : GHService } - - - diff --git a/orchestration/gh.py b/orchestration/gh.py index 6727569..36277a8 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -6,8 +6,8 @@ from scm_services import SCMService, Cloner from workflows.state_service import WorkflowStateService -class GithubOrchestrator(OrchestratorBase): +class GithubOrchestrator(OrchestratorBase): __install_action_query = parse("$.action") __install_sender_query = parse("$.sender.login") __install_target_query = parse("$.installation.account.login") @@ -38,7 +38,6 @@ class GithubOrchestrator(OrchestratorBase): } - @staticmethod def log(): return logging.getLogger("BitBucketDataCenterOrchestrator") @@ -102,8 +101,6 @@ async def __log_app_install(self, cxone_service : CxOneService, scm_service : SC if not bad and not warned: GithubOrchestrator.log().info("The GitHub app appears to be properly configured.") - - def __installation_route_urls(self): return [GithubOrchestrator.__install_route_url_query.find(self.__json)[0].value] @@ -173,7 +170,22 @@ async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_se return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) + async def _get_protected_branches(self, scm_service : SCMService) -> list: + page_max = 1 + + # TODO: need a pager + # default branch is a protected branch, but they have rules. + # use default branch if there are no protected branches. + + + # this would list all protected branches + + foo = await scm_service.exec("GET", f"/repos/{self._repo_organization}/{self._repo_project_key}/branches", + query = {"protected" : True, "per_page" : page_max}, + event_msg=self.__json) + + raise NotImplementedError("_get_protected_branches") diff --git a/scm_services/__init__.py b/scm_services/__init__.py index c80af5e..2dbdb0a 100644 --- a/scm_services/__init__.py +++ b/scm_services/__init__.py @@ -1,82 +1,7 @@ from .cloner import Cloner -from requests.auth import AuthBase from .scm import SCMService from .adoe import ADOEService from .bbdc import BBDCService from .gh import GHService -from api_utils import auth_basic, auth_bearer, GithubOauth -class ScmCloneAuthSupportException(Exception): - pass - - -def bitbucketdc_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None, oauth_secret : str=None, oauth_id : str=None) -> Cloner: - if oauth_secret is not None or oauth_id is not None: - raise ScmCloneAuthSupportException("OAuth clone authentication not supported for BBDC") - - if username is not None and password is not None: - return Cloner.using_basic_auth(username, password, True) - - if token is not None: - return Cloner.using_token_auth(token, username) - - if ssh_path is not None: - return Cloner.using_ssh_auth(ssh_path, ssh_port) - - return None - -def adoe_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None, oauth_secret : str=None, oauth_id : str=None) -> Cloner: - if oauth_secret is not None or oauth_id is not None: - raise ScmCloneAuthSupportException("OAuth clone authentication not supported for ADO") - - if username is not None and password is not None: - return Cloner.using_basic_auth(username, password, True) - - if token is not None: - return Cloner.using_basic_auth("", token, True) - - if ssh_path is not None: - return Cloner.using_ssh_auth(ssh_path, ssh_port) - - -def gh_cloner_factory(username=None, password=None, token=None, ssh_path=None, ssh_port=None, oauth_secret : str=None, oauth_id : str=None) -> Cloner: - return Cloner() - - -class ScmApiAuthSupportException(Exception): - pass - - -def adoe_api_auth_factory(username : str=None, password : str=None, token : str=None, oauth_secret : str=None, oauth_id : str=None) -> AuthBase: - if oauth_secret is not None or oauth_id is not None: - raise ScmApiAuthSupportException("OAuth API authentication not supported for ADO") - - if token is not None: - return auth_basic("", token) - elif username is not None and password is not None: - return auth_basic(username, password) - else: - raise ScmApiAuthSupportException("Unable to determine API auth method.") - - -def bbdc_api_auth_factory(username=None, password=None, token=None, oauth_secret : str=None, oauth_id : str=None) -> AuthBase: - if oauth_secret is not None or oauth_id is not None: - raise ScmApiAuthSupportException("OAuth API authentication not supported for BBDC") - - if token is not None: - return auth_bearer(token) - elif username is not None and password is not None: - return auth_basic(username, password) - else: - raise ScmApiAuthSupportException("Unable to determine API auth method.") - -def github_api_auth_factory(username=None, password=None, token=None, oauth_secret : str=None, oauth_id : str=None) -> AuthBase: - if token is not None: - return auth_bearer(token) - elif username is not None and password is not None: - return auth_basic(username, password) - elif oauth_id is not None and oauth_secret is not None: - return GithubOauth(oauth_id, oauth_secret) - else: - raise ScmApiAuthSupportException("Unable to determine API auth method.") diff --git a/scm_services/adoe.py b/scm_services/adoe.py index 5366cd7..694cfa0 100644 --- a/scm_services/adoe.py +++ b/scm_services/adoe.py @@ -4,6 +4,7 @@ from typing import Union, Dict from datetime import datetime, UTC import markdown as md +from typing import Dict class ADOEService(SCMService): @@ -75,7 +76,7 @@ async def __create_pr_thread(self, organization : str, project : str, repo_slug async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, - scanid : str, full_markdown : str, summary_markdown : str): + scanid : str, full_markdown : str, summary_markdown : str, event_msg : Dict=None): existing_thread = await self.__get_pr_thread(organization, project, repo_slug, pr_number) content = md.markdown(full_markdown, extensions=['tables']) diff --git a/scm_services/bbdc.py b/scm_services/bbdc.py index e9a81c5..3527a6b 100644 --- a/scm_services/bbdc.py +++ b/scm_services/bbdc.py @@ -2,6 +2,7 @@ from cxone_api.util import json_on_ok import json from workflows.pr import PullRequestDecoration +from typing import Dict class BBDCService(SCMService): __max_content_chars = 32000 @@ -53,7 +54,8 @@ async def __find_existing_comment(self, project : str, repo_slug : str, pr_numbe return int(comment['id']), int(comment['version']) return None, None - async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, summary_markdown : str): + async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, + summary_markdown : str, event_msg : Dict=None): id, version = await self.__find_existing_comment(project, repo_slug, pr_number) content = full_markdown if len(full_markdown) <= BBDCService.__max_content_chars else summary_markdown diff --git a/scm_services/cloner.py b/scm_services/cloner.py index edf7101..d5e6f58 100644 --- a/scm_services/cloner.py +++ b/scm_services/cloner.py @@ -1,5 +1,6 @@ import os, tempfile, shutil, shlex, subprocess, asyncio, logging, urllib, base64, re from cxoneflow_logging import SecretRegistry +from pathlib import Path class Cloner: @@ -44,7 +45,7 @@ def using_basic_auth(username, password, in_header=False): return retval @staticmethod - def using_token_auth(token, username=None): + def using_token_auth(token): Cloner.log().debug("Clone config: using_token_auth") retval = Cloner() @@ -59,7 +60,7 @@ def using_token_auth(token, username=None): return retval @staticmethod - def using_ssh_auth(ssh_private_key_file, ssh_port): + def using_ssh_auth(ssh_private_key_file : Path, ssh_port : int): Cloner.log().debug("Clone config: using_ssh_auth") retval = Cloner() diff --git a/scm_services/gh.py b/scm_services/gh.py index 06444aa..869fbd0 100644 --- a/scm_services/gh.py +++ b/scm_services/gh.py @@ -1,4 +1,6 @@ from .scm import SCMService +# from api_utils.apisession import APISession +from scm_services.cloner import Cloner class GHService(SCMService): diff --git a/scm_services/scm.py b/scm_services/scm.py index 4c88e93..8818a62 100644 --- a/scm_services/scm.py +++ b/scm_services/scm.py @@ -1,8 +1,8 @@ import logging -from requests import Request -from api_utils import APISession +from api_utils.apisession import APISession from scm_services.cloner import Cloner - +from typing import Dict, Any +from requests import Response class SCMService: @@ -17,6 +17,7 @@ def __init__(self, moniker : str, api_session : APISession, shared_secret : str, self.__cloner = cloner self.__moniker = moniker + @property def moniker(self): return self.__moniker @@ -32,10 +33,11 @@ def shared_secret(self): def _form_url(self, url_path, anchor=None, **kwargs): return self.__session._form_url(url_path, anchor, **kwargs) - async def exec(self, method : str, path : str, query=None, body=None, extra_headers=None): - return await self.__session.exec(method, path, query, body, extra_headers) + async def exec(self, method : str, path : str, query : Dict=None, body : Any=None, extra_headers : Dict=None, event_msg : Dict=None) -> Response: + return await self.__session.exec(event_msg, method, path, query, body, extra_headers) - async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, summary_markdown : str): + async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, + summary_markdown : str, event_msg : Dict=None): raise NotImplementedError("exec_pr_decorate") def create_code_permalink(self, organization : str, project : str, repo_slug : str, branch : str, code_path : str, code_line : str): From 89c2af53377136692af1016f0422c7039313c775 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 20 Sep 2024 12:16:04 +0000 Subject: [PATCH 07/35] avoid problems with time drift --- api_utils/apisession.py | 4 ++-- api_utils/auth_factories.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/api_utils/apisession.py b/api_utils/apisession.py index f9a7834..6ef7e39 100644 --- a/api_utils/apisession.py +++ b/api_utils/apisession.py @@ -58,8 +58,8 @@ async def exec(self, event_msg : Dict, method : str, path : str, query : Dict = APISession.log().debug(f"Executing: {prepStr} #{tryCount}") response = await asyncio.to_thread(request, method=method, url=url, params=query, - data=body, headers=headers, auth=await self.__auth_factory.make_auth(event_msg, self._api_endpoint), timeout=self.__timeout, - proxies=self.__proxies, verify=self.__verify) + data=body, headers=headers, auth=await self.__auth_factory.make_auth(event_msg, self._api_endpoint, tryCount > 0), + timeout=self.__timeout, proxies=self.__proxies, verify=self.__verify) logStr = f"{response.status_code}: {response.reason} {prepStr}" APISession.log().debug(f"Response #{tryCount}: {logStr} : {response.text}") diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index 3469311..0b1b93e 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -14,14 +14,14 @@ class AuthFactoryException(BaseException): pass class AuthFactory: - async def make_auth(self, event_context : Dict=None, api_url : str=None) -> AuthBase: + async def make_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: raise NotImplementedError("make_auth") class StaticAuthFactory(AuthFactory): def __init__(self, static_auth : AuthBase): self.__auth = static_auth - async def make_auth(self, event_context : Dict=None, api_url : str=None) -> AuthBase: + async def make_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: return self.__auth @@ -50,7 +50,7 @@ def __encoded_jwt_factory(self, install_id : int) -> str: return jwt.encode(payload, self.__pkey, algorithm='RS256') - async def make_auth(self, event_context : Dict=None, api_url : str=None) -> AuthBase: + async def make_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: if event_context is None or api_url is None: raise AuthFactoryException("Event context and API url are required.") @@ -66,6 +66,7 @@ async def make_auth(self, event_context : Dict=None, api_url : str=None) -> Auth token_tuple = None + with GithubAppAuthFactory.__lock: if install_id in GithubAppAuthFactory.__token_cache.keys(): token_tuple = tuple(GithubAppAuthFactory.__token_cache[install_id]) @@ -73,7 +74,7 @@ async def make_auth(self, event_context : Dict=None, api_url : str=None) -> Auth if datetime.now(exp.tzinfo) >= exp: token_tuple = None - if token_tuple is None: + if token_tuple is None or force_reauth: token_response = json_on_ok(await asyncio.to_thread(request, method="POST", url=f"{api_url.rstrip("/")}/app/installations/{install_id}/access_tokens", headers = {"User-Agent" : __agent__}, auth=HTTPBearerAuth(self.__encoded_jwt_factory(app_id)))) From b10b6440e6592f52786380d3665ba4c587187227 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 20 Sep 2024 14:59:21 +0000 Subject: [PATCH 08/35] generic api pager --- api_utils/pagers.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 api_utils/pagers.py diff --git a/api_utils/pagers.py b/api_utils/pagers.py new file mode 100644 index 0000000..2326405 --- /dev/null +++ b/api_utils/pagers.py @@ -0,0 +1,41 @@ +from typing import Callable, Awaitable, List, Any, Dict +from requests import Response + + +async def async_api_page_generator(coro : Awaitable[Response], + data_extractor : Callable[[Response], List], kwargs_gen : Callable[[int], Dict]) -> Any: + + """_summary_ + + A generator for paging API calls. + + Args: + coro - an awaitable coroutine that will be called with kwargs provideded by kwargs_gen + + data_extractor - A callable that is given a response object returned by coro and is expected to + returns a tuple containing elements: + 0: A list of elements that are provided in the generator. None or an empty stops the generator. + 1: A boolean indicating this is the last page. + + kwargs_gen - A method that returns a list used as kwargs when executing coro. A single int + parameter is passed to indicate the current offset count. + + Yields: + Any: An extracted object as returned by data_extractor callable. + """ + offset = 0 + buf = [] + last_page = False + + while True: + if len(buf) == 0 and not last_page: + buf, last_page = data_extractor(await coro(**(kwargs_gen(offset)))) + + if buf is None or len(buf) == 0: + return + offset = offset + 1 + elif len(buf) == 0 and last_page: + return + + yield buf.pop() + From 6f6c24a87981db1ddf2ddad042b799a3954e436b Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 20 Sep 2024 14:59:50 +0000 Subject: [PATCH 09/35] logging adjusted --- api_utils/auth_factories.py | 7 ++++--- orchestration/adoe.py | 4 ---- orchestration/bbdc.py | 4 ---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index 0b1b93e..aea18ca 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -14,6 +14,10 @@ class AuthFactoryException(BaseException): pass class AuthFactory: + @classmethod + def log(clazz): + return logging.getLogger(clazz.__name__) + async def make_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: raise NotImplementedError("make_auth") @@ -33,9 +37,6 @@ class GithubAppAuthFactory(AuthFactory): __event_installation_id = parse("$.installation.id") __event_app_id = parse("$.sender.id") - @staticmethod - def log(): - return logging.getLogger("GithubAppAuthFactory") def __init__(self, private_key : str): self.__pkey = private_key diff --git a/orchestration/adoe.py b/orchestration/adoe.py index 0e3b724..4c7603b 100644 --- a/orchestration/adoe.py +++ b/orchestration/adoe.py @@ -36,10 +36,6 @@ class AzureDevOpsEnterpriseOrchestrator(OrchestratorBase): __pr_state_query = parse("$.resource.status") - @staticmethod - def log() -> logging.Logger: - return logging.getLogger("AzureDevOpsEnterpriseOrchestrator") - @property def config_key(self): return "adoe" diff --git a/orchestration/bbdc.py b/orchestration/bbdc.py index 02195ab..e5b7dd1 100644 --- a/orchestration/bbdc.py +++ b/orchestration/bbdc.py @@ -34,10 +34,6 @@ class BitBucketDataCenterOrchestrator(OrchestratorBase): __pr_state_query = parse("$.pullRequest.state") - @staticmethod - def log(): - return logging.getLogger("BitBucketDataCenterOrchestrator") - @property def config_key(self): return "bbdc" From 5863ec2ced4e4af9aad450cacdff27134669b27a Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 20 Sep 2024 15:00:01 +0000 Subject: [PATCH 10/35] protected branches --- orchestration/base.py | 6 +++--- orchestration/gh.py | 46 +++++++++++++++++++++++++++++-------------- test.py | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 test.py diff --git a/orchestration/base.py b/orchestration/base.py index 5555dcc..d1e83e6 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -15,9 +15,9 @@ class OrchestratorBase: def normalize_branch_name(branch): return branch.split("/")[-1:].pop() - @staticmethod - def log() -> logging.Logger: - return logging.getLogger("OrchestratorBase") + @classmethod + def log(clazz) -> logging.Logger: + return logging.getLogger(clazz.__name__) def __init__(self, headers, webhook_payload): self.__webhook_payload = webhook_payload diff --git a/orchestration/gh.py b/orchestration/gh.py index 36277a8..0cddc89 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -1,13 +1,19 @@ from .base import OrchestratorBase from api_utils import signature +from api_utils.pagers import async_api_page_generator from jsonpath_ng import parse -import logging, json +import json from cxone_service import CxOneService +from cxone_api.util import json_on_ok from scm_services import SCMService, Cloner from workflows.state_service import WorkflowStateService - +from requests import Response class GithubOrchestrator(OrchestratorBase): + + __api_page_max = 100 + + __install_action_query = parse("$.action") __install_sender_query = parse("$.sender.login") __install_target_query = parse("$.installation.account.login") @@ -25,6 +31,9 @@ class GithubOrchestrator(OrchestratorBase): __push_project_key_query = parse("$.repository.name") __push_org_key_query = parse("$.repository.owner.name") + __branch_names_extract = parse("$.[*].name") + __default_branch_name_extract = parse("$.default_branch") + __expected_events = ['pull_request', 'pull_request_review', 'push'] __expected_permissions = { "contents" : "read", @@ -38,10 +47,6 @@ class GithubOrchestrator(OrchestratorBase): } - @staticmethod - def log(): - return logging.getLogger("BitBucketDataCenterOrchestrator") - @property def config_key(self): return "gh" @@ -172,21 +177,32 @@ async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_se async def _get_protected_branches(self, scm_service : SCMService) -> list: - page_max = 1 + ret_branches = [] + def data_extractor(resp : Response): + if resp.ok: + json = resp.json() + v = [b.value for b in GithubOrchestrator.__branch_names_extract.find(json)] + return v, len(v) < GithubOrchestrator.__api_page_max - # TODO: need a pager - # default branch is a protected branch, but they have rules. - # use default branch if there are no protected branches. + return None + def args_gen(offset : int): + return { + "method" : "GET", + "path" : f"/repos/{self._repo_organization}/{self._repo_project_key}/branches", + "query" : { "protected" : True, "per_page" : GithubOrchestrator.__api_page_max, "page" : offset + 1}, + "event_msg" : self.__json + } - # this would list all protected branches + async for branch in async_api_page_generator(scm_service.exec, data_extractor, args_gen): + ret_branches.append(branch) - foo = await scm_service.exec("GET", f"/repos/{self._repo_organization}/{self._repo_project_key}/branches", - query = {"protected" : True, "per_page" : page_max}, - event_msg=self.__json) + if len(ret_branches) == 0: + ret_branches.append(GithubOrchestrator.__default_branch_name_extract.find( + json_on_ok(await scm_service.exec("GET", f"/repos/{self._repo_organization}/{self._repo_project_key}", event_msg=self.__json)))[0].value) + return ret_branches - raise NotImplementedError("_get_protected_branches") @property diff --git a/test.py b/test.py new file mode 100644 index 0000000..e7ea4ff --- /dev/null +++ b/test.py @@ -0,0 +1,39 @@ +import jwt, time, requests + +# Add: PyJWT, cryptography + + + +pem = "./secrets/ghe-app-priv-key" +app_id = 5 + + +with open(pem, "rb") as f: + signing_key = f.read() + +payload = { + 'iat' : int(time.time()), + "exp" : int(time.time()) + 600, + 'iss' : app_id, + 'alg' : "RS256" +} + +encoded_jwt = jwt.encode(payload, signing_key, algorithm='RS256') + +print(f"JWT: {encoded_jwt}") + +resp = requests.post(f"http://ghe.pot8o.site/api/v3/app/installations/26/access_tokens", headers = { + 'Accept' : 'application/vnd.github+json', + 'Authorization' : f"Bearer {encoded_jwt}", + 'X-GitHub-Api-Version' : '2022-11-28' + }) + +# resp = requests.get(f"http://ghe.pot8o.site/api/v3/app/installations", headers = { +# 'Accept' : 'application/vnd.github+json', +# 'Authorization' : f"Bearer {encoded_jwt}", +# 'X-GitHub-Api-Version' : '2022-11-28'}) + +print(resp.status_code) +print(resp.url) +print(resp.headers) +print(resp.text) From 74d5bc2e286ae711180acb98d962f2ab27e26c01 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 20 Sep 2024 21:45:14 +0000 Subject: [PATCH 11/35] wip cloner broken --- api_utils/apisession.py | 2 +- api_utils/auth_factories.py | 29 ++++++++++++++--------- config/__init__.py | 6 ++++- orchestration/base.py | 2 +- orchestration/gh.py | 4 ++++ scm_services/cloner.py | 46 +++++++++++++++++++++++++++++-------- 6 files changed, 65 insertions(+), 24 deletions(-) diff --git a/api_utils/apisession.py b/api_utils/apisession.py index 6ef7e39..d8c2407 100644 --- a/api_utils/apisession.py +++ b/api_utils/apisession.py @@ -58,7 +58,7 @@ async def exec(self, event_msg : Dict, method : str, path : str, query : Dict = APISession.log().debug(f"Executing: {prepStr} #{tryCount}") response = await asyncio.to_thread(request, method=method, url=url, params=query, - data=body, headers=headers, auth=await self.__auth_factory.make_auth(event_msg, self._api_endpoint, tryCount > 0), + data=body, headers=headers, auth=await self.__auth_factory.get_auth(event_msg, self._api_endpoint, tryCount > 0), timeout=self.__timeout, proxies=self.__proxies, verify=self.__verify) logStr = f"{response.status_code}: {response.reason} {prepStr}" diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index aea18ca..b683ca4 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -1,6 +1,6 @@ from requests.auth import AuthBase from requests import request -from typing import Dict +from typing import Dict, Tuple from jsonpath_ng import parse import jwt, time, asyncio, logging from datetime import datetime @@ -18,14 +18,18 @@ class AuthFactory: def log(clazz): return logging.getLogger(clazz.__name__) - async def make_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: - raise NotImplementedError("make_auth") + async def get_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: + raise NotImplementedError("get_auth") + + async def get_token(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> str: + raise NotImplementedError("get_token") + class StaticAuthFactory(AuthFactory): def __init__(self, static_auth : AuthBase): self.__auth = static_auth - async def make_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: + async def get_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: return self.__auth @@ -50,11 +54,9 @@ def __encoded_jwt_factory(self, install_id : int) -> str: } return jwt.encode(payload, self.__pkey, algorithm='RS256') + + async def __get_token_tuple(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False): - async def make_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: - if event_context is None or api_url is None: - raise AuthFactoryException("Event context and API url are required.") - install_id_found = GithubAppAuthFactory.__event_installation_id.find(event_context) if len(install_id_found) == 0: raise AuthFactoryException("GitHub installation id was not found in the event payload.") @@ -66,7 +68,6 @@ async def make_auth(self, event_context : Dict=None, api_url : str=None, force_r app_id = app_id_found[0].value token_tuple = None - with GithubAppAuthFactory.__lock: if install_id in GithubAppAuthFactory.__token_cache.keys(): @@ -84,10 +85,16 @@ async def make_auth(self, event_context : Dict=None, api_url : str=None, force_r with GithubAppAuthFactory.__lock: GithubAppAuthFactory.__token_cache[install_id] = token_tuple + + return token_tuple + async def get_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: + if event_context is None or api_url is None: + raise AuthFactoryException("Event context and API url are required.") + return HTTPBearerAuth ((await self.__get_token_tuple(event_context, api_url, force_reauth))[0]) - return HTTPBearerAuth (token_tuple[0]) - + async def get_token(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> str: + return (await self.__get_token_tuple(event_context, api_url, force_reauth))[0] diff --git a/config/__init__.py b/config/__init__.py index 256db30..28c62a9 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -477,7 +477,11 @@ def __adoe_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: @staticmethod def __gh_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: - return Cloner() + ret_cloner = CxOneFlowConfig.__bitbucketdc_cloner_factory(config_path, config_dict) + if ret_cloner is None and 'app-private-key' in config_dict.keys(): + ret_cloner = Cloner.using_github_app_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "app-private-key", config_dict)) + + return ret_cloner @staticmethod diff --git a/orchestration/base.py b/orchestration/base.py index d1e83e6..252a4e2 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -79,7 +79,7 @@ async def __exec_scan(self, cxone_service : CxOneService, scm_service : SCMServi check = perf_counter_ns() OrchestratorBase.log().debug("Starting clone...") - async with scm_service.cloner.clone(clone_url) as clone_worker: + async with await scm_service.cloner.clone(clone_url) as clone_worker: code_path = await clone_worker.loc() await scm_service.cloner.reset_head(code_path, source_hash) diff --git a/orchestration/gh.py b/orchestration/gh.py index 0cddc89..627eb43 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -224,6 +224,10 @@ def _repo_name(self) -> str: def _repo_clone_url(self, cloner) -> str: return self.__clone_urls[cloner.select_protocol_from_supported(self.__clone_urls.keys())] + async def get_cxone_project_name(self) -> str: + return f"{self._repo_organization}/{self._repo_slug}" + + __workflow_map = { "installation" : __log_app_install, "installation_repositories" : __log_app_install, diff --git a/scm_services/cloner.py b/scm_services/cloner.py index d5e6f58..8244bc3 100644 --- a/scm_services/cloner.py +++ b/scm_services/cloner.py @@ -1,6 +1,8 @@ import os, tempfile, shutil, shlex, subprocess, asyncio, logging, urllib, base64, re from cxoneflow_logging import SecretRegistry from pathlib import Path +from typing import Dict, List +from api_utils.auth_factories import GithubAppAuthFactory class Cloner: @@ -13,18 +15,18 @@ class Cloner: def __init__(self): self.__env = dict(os.environ) - @staticmethod - def log(): - return logging.getLogger("Cloner") + @classmethod + def log(clazz) -> logging.Logger: + return logging.getLogger(clazz.__name__) @staticmethod - def __insert_creds_in_url(url, username, password): + def __insert_creds_in_url(url : str, username : str, password : str) -> str: split = urllib.parse.urlsplit(url) new_netloc = f"{urllib.parse.quote(username, safe='') if username is not None else 'git'}:{SecretRegistry.register(urllib.parse.quote(password, safe=''))}@{split.netloc}" return urllib.parse.urlunsplit((split.scheme, new_netloc, split.path, split.query, split.fragment)) @staticmethod - def using_basic_auth(username, password, in_header=False): + def using_basic_auth(username : str, password : str, in_header : bool=False): Cloner.log().debug("Clone config: using_basic_auth") retval = Cloner() @@ -45,7 +47,7 @@ def using_basic_auth(username, password, in_header=False): return retval @staticmethod - def using_token_auth(token): + def using_token_auth(token : str): Cloner.log().debug("Clone config: using_token_auth") retval = Cloner() @@ -78,6 +80,19 @@ def using_ssh_auth(ssh_private_key_file : Path, ssh_port : int): retval.__fix_clone_url = lambda url: url return retval + + @staticmethod + def using_github_app_auth(app_private_key : str): + Cloner.log().debug("Clone config: using_github_app_auth") + + retval = GithubAppCloner(GithubAppAuthFactory(app_private_key)) + retval.__protocol_matcher = Cloner.__https_matcher + retval.__supported_protocols = Cloner.__http_protocols + retval.__port = None + retval.__fix_clone_url = lambda url: url + # retval.__clone_cmd_stub = ["git", "clone", "-c"] + + return retval def select_protocol_from_supported(self, protocol_list): for x in protocol_list: @@ -92,13 +107,16 @@ def supported_protocols(self): @property def destination_port(self): return self.__port - - def clone(self, clone_url): + + async def _get_clone_cmd_stub(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> List: + return self.__clone_cmd_stub + + async def clone(self, clone_url, event_context : Dict=None, api_url : str=None, force_reauth : bool=False): Cloner.log().debug(f"Clone Execution for: {clone_url}") fixed_clone_url = self.__fix_clone_url(clone_url) clone_output_loc = tempfile.TemporaryDirectory(delete=False) - cmd = self.__clone_cmd_stub + [fixed_clone_url, clone_output_loc.name] + cmd = await self._get_clone_cmd_stub(event_context, api_url, force_reauth) + [fixed_clone_url, clone_output_loc.name] Cloner.log().debug(cmd) thread = asyncio.to_thread(subprocess.run, cmd, capture_output=True, env=self.__env, check=True) @@ -123,7 +141,7 @@ def __init__(self, clone_thread, clone_dest_path): self.__clone_thread = clone_thread - async def loc(self): + async def loc(self) -> str: try: completed = await self.__clone_thread self.__log.debug(f"Clone task: return code [{completed.returncode}] stdout: [{completed.stdout}] stderr: [{completed.stderr}]") @@ -140,4 +158,12 @@ async def __aexit__(self, exc_type, exc, tb): self.__clone_out_tempdir.cleanup() +class GithubAppCloner(Cloner): + def __init__(self, auth_factory : GithubAppAuthFactory): + self.__auth_factory = auth_factory + + async def _get_clone_cmd_stub(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> List: + token = await self.__auth_factory.get_token(event_context, api_url, force_reauth) + return ["git", "clone", "-c", + f"http.extraHeader=Authorization: Bearer {token}"] From 807e73b2fcd92df6717a644325f0eaab1f6955f5 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Mon, 23 Sep 2024 18:35:58 +0000 Subject: [PATCH 12/35] ghe clone impl --- api_utils/apisession.py | 25 +++++---- api_utils/auth_factories.py | 32 ++++++----- config/__init__.py | 43 ++++++++------- orchestration/adoe.py | 3 +- orchestration/base.py | 8 ++- orchestration/bbdc.py | 4 +- orchestration/gh.py | 5 +- scm_services/__init__.py | 1 - scm_services/cloner.py | 102 ++++++++++++++++++------------------ wsgi.py | 2 +- 10 files changed, 122 insertions(+), 103 deletions(-) diff --git a/api_utils/apisession.py b/api_utils/apisession.py index d8c2407..47c1c5f 100644 --- a/api_utils/apisession.py +++ b/api_utils/apisession.py @@ -17,12 +17,11 @@ class APISession: def log(clazz): return logging.getLogger(clazz.__name__) - def __init__(self, api_base_endpoint : str, api_suffix : str, auth : AuthFactory, timeout : int = 60, retries : int = 3, proxies : Dict = None, ssl_verify : Union[bool, str] = True): + def __init__(self, api_endpoint : str, auth : AuthFactory, timeout : int = 60, retries : int = 3, proxies : Dict = None, ssl_verify : Union[bool, str] = True): self.__headers = { "User-Agent" : __agent__ } - self.__base_endpoint = api_base_endpoint - self.__api_suffix = api_suffix + self.__api_endpoint = api_endpoint self.__timeout = timeout self.__retries = retries @@ -30,17 +29,21 @@ def __init__(self, api_base_endpoint : str, api_suffix : str, auth : AuthFactory self.__proxies = proxies self.__auth_factory = auth + @staticmethod + def form_api_endpoint(base_endpoint : str, suffix : str): + ret = base_endpoint.rstrip("/") + if suffix is not None and len(suffix) > 0: + ret = f"{ret}/{suffix.lstrip("/").rstrip("/")}" + return ret + + @property - def _api_endpoint(self): - base = self.__base_endpoint.rstrip("/") - if self.__api_suffix is not None and len(self.__api_suffix) > 0: - base = f"{base}/{self.__api_suffix.lstrip("/").rstrip("/")}" - - return base + def api_endpoint(self): + return self.__api_endpoint def _form_url(self, url_path, anchor=None, **kwargs): - base = self._api_endpoint + base = self.api_endpoint suffix = urllib.parse.quote(url_path.lstrip("/")) args = [f"{x}={urllib.parse.quote(str(kwargs[x]))}" for x in kwargs.keys()] return f"{base}/{suffix}{"?" if len(args) > 0 else ""}{"&".join(args)}{f"#{anchor}" if anchor is not None else ""}" @@ -58,7 +61,7 @@ async def exec(self, event_msg : Dict, method : str, path : str, query : Dict = APISession.log().debug(f"Executing: {prepStr} #{tryCount}") response = await asyncio.to_thread(request, method=method, url=url, params=query, - data=body, headers=headers, auth=await self.__auth_factory.get_auth(event_msg, self._api_endpoint, tryCount > 0), + data=body, headers=headers, auth=await self.__auth_factory.get_auth(event_msg, tryCount > 0), timeout=self.__timeout, proxies=self.__proxies, verify=self.__verify) logStr = f"{response.status_code}: {response.reason} {prepStr}" diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index b683ca4..e85c808 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -1,6 +1,6 @@ from requests.auth import AuthBase from requests import request -from typing import Dict, Tuple +from typing import Dict from jsonpath_ng import parse import jwt, time, asyncio, logging from datetime import datetime @@ -18,10 +18,10 @@ class AuthFactory: def log(clazz): return logging.getLogger(clazz.__name__) - async def get_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: + async def get_auth(self, event_context : Dict=None, force_reauth : bool=False) -> AuthBase: raise NotImplementedError("get_auth") - async def get_token(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> str: + async def get_token(self, event_context : Dict=None, force_reauth : bool=False) -> str: raise NotImplementedError("get_token") @@ -29,7 +29,7 @@ class StaticAuthFactory(AuthFactory): def __init__(self, static_auth : AuthBase): self.__auth = static_auth - async def get_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: + async def get_auth(self, event_context : Dict=None, force_reauth : bool=False) -> AuthBase: return self.__auth @@ -42,8 +42,9 @@ class GithubAppAuthFactory(AuthFactory): __event_app_id = parse("$.sender.id") - def __init__(self, private_key : str): + def __init__(self, private_key : str, api_url : str): self.__pkey = private_key + self.__api_url = api_url def __encoded_jwt_factory(self, install_id : int) -> str: payload = { @@ -55,7 +56,7 @@ def __encoded_jwt_factory(self, install_id : int) -> str: return jwt.encode(payload, self.__pkey, algorithm='RS256') - async def __get_token_tuple(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False): + async def __get_token_tuple(self, event_context : Dict=None, force_reauth : bool=False): install_id_found = GithubAppAuthFactory.__event_installation_id.find(event_context) if len(install_id_found) == 0: @@ -76,8 +77,9 @@ async def __get_token_tuple(self, event_context : Dict=None, api_url : str=None, if datetime.now(exp.tzinfo) >= exp: token_tuple = None - if token_tuple is None or force_reauth: - token_response = json_on_ok(await asyncio.to_thread(request, method="POST", url=f"{api_url.rstrip("/")}/app/installations/{install_id}/access_tokens", + if token_tuple is None or force_reauth: + token_response = json_on_ok(await asyncio.to_thread(request, method="POST", + url=f"{self.__api_url.rstrip("/")}/app/installations/{install_id}/access_tokens", headers = {"User-Agent" : __agent__}, auth=HTTPBearerAuth(self.__encoded_jwt_factory(app_id)))) @@ -88,13 +90,15 @@ async def __get_token_tuple(self, event_context : Dict=None, api_url : str=None, return token_tuple - async def get_auth(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> AuthBase: - if event_context is None or api_url is None: - raise AuthFactoryException("Event context and API url are required.") - return HTTPBearerAuth ((await self.__get_token_tuple(event_context, api_url, force_reauth))[0]) + async def get_auth(self, event_context : Dict=None, force_reauth : bool=False) -> AuthBase: + if event_context is None: + raise AuthFactoryException("Event context is required.") + return HTTPBearerAuth ((await self.__get_token_tuple(event_context, force_reauth))[0]) - async def get_token(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> str: - return (await self.__get_token_tuple(event_context, api_url, force_reauth))[0] + async def get_token(self, event_context : Dict=None, force_reauth : bool=False) -> str: + if event_context is None: + raise AuthFactoryException("Event context is required.") + return (await self.__get_token_tuple(event_context, force_reauth))[0] diff --git a/config/__init__.py b/config/__init__.py index 28c62a9..301248a 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -3,7 +3,8 @@ from pathlib import Path import re import yaml, logging, cxone_api as cx, os -from scm_services import SCMService, ADOEService, BBDCService, GHService, Cloner +from scm_services import SCMService, ADOEService, BBDCService, GHService +from scm_services.cloner import Cloner from api_utils import auth_basic, auth_bearer from api_utils.apisession import APISession from api_utils.auth_factories import AuthFactory, GithubAppAuthFactory @@ -347,12 +348,12 @@ def __cxone_client_factory(config_path, **kwargs): __scm_config_tuples_by_service_moniker = {} @staticmethod - def __scm_api_auth_factory(api_auth_factory, config_dict, config_path): + def __scm_api_auth_factory(api_url : str, api_auth_factory, config_dict, config_path): retval = None if config_dict is not None and len(config_dict.keys()) > 0: - retval = api_auth_factory(config_path, config_dict) + retval = api_auth_factory(api_url, config_path, config_dict) if retval is None: raise ConfigurationException(f"{config_path} SCM API authorization configuration is invalid!") @@ -361,9 +362,9 @@ def __scm_api_auth_factory(api_auth_factory, config_dict, config_path): @staticmethod - def __cloner_factory(scm_cloner_factory, clone_auth_dict, config_path): + def __cloner_factory(api_session : APISession, scm_cloner_factory, clone_auth_dict, config_path): - retval = scm_cloner_factory(Path(CxOneFlowConfig.__secret_root), clone_auth_dict) + retval = scm_cloner_factory(api_session, Path(CxOneFlowConfig.__secret_root), clone_auth_dict) if retval is None: raise ConfigurationException(f"{config_path} SCM clone authorization configuration is invalid!") @@ -395,9 +396,11 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config api_auth_dict = CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'api-auth', connection_config_dict) - api_session = APISession(CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict), \ - CxOneFlowConfig.__get_value_for_key_or_default('api-url-suffix', connection_config_dict, None), \ - CxOneFlowConfig.__scm_api_auth_factory(api_auth_factory, api_auth_dict, f"{config_path}/connection/api-auth"), \ + api_url = APISession.form_api_endpoint(CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict), + CxOneFlowConfig.__get_value_for_key_or_default('api-url-suffix', connection_config_dict, None)) + + api_session = APISession(api_url, \ + CxOneFlowConfig.__scm_api_auth_factory(api_url, api_auth_factory, api_auth_dict, f"{config_path}/connection/api-auth"), \ CxOneFlowConfig.__get_value_for_key_or_default('timeout-seconds', connection_config_dict, 60), \ CxOneFlowConfig.__get_value_for_key_or_default('retries', connection_config_dict, 3), \ CxOneFlowConfig.__get_value_for_key_or_default('proxies', connection_config_dict, None), \ @@ -415,7 +418,8 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config clone_auth_dict = api_auth_dict clone_config_path = f"{config_path}/connection/api-auth" - scm_service = scm_class(service_moniker, api_session, scm_shared_secret, CxOneFlowConfig.__cloner_factory(cloner_factory, clone_auth_dict, clone_config_path)) + scm_service = scm_class(service_moniker, api_session, scm_shared_secret, + CxOneFlowConfig.__cloner_factory(api_session, cloner_factory, clone_auth_dict, clone_config_path)) return repo_matcher, cxone_service, scm_service, workflow_service_client @@ -442,7 +446,7 @@ def __has_ssh_auth(config_dict : Dict) -> bool: @staticmethod - def __bitbucketdc_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: + def __bitbucketdc_cloner_factory(api_session : APISession, config_path : str, config_dict : Dict) -> Cloner: if CxOneFlowConfig.__has_basic_auth(config_dict): return Cloner.using_basic_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict), True) @@ -458,7 +462,7 @@ def __bitbucketdc_cloner_factory(config_path : str, config_dict : Dict) -> Clone return None @staticmethod - def __adoe_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: + def __adoe_cloner_factory(api_session : APISession, config_path : str, config_dict : Dict) -> Cloner: if CxOneFlowConfig.__has_basic_auth(config_dict): return Cloner.using_basic_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict), True) @@ -476,16 +480,16 @@ def __adoe_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: return None @staticmethod - def __gh_cloner_factory(config_path : str, config_dict : Dict) -> Cloner: - ret_cloner = CxOneFlowConfig.__bitbucketdc_cloner_factory(config_path, config_dict) + def __gh_cloner_factory(api_session : APISession, config_path : str, config_dict : Dict) -> Cloner: + ret_cloner = CxOneFlowConfig.__bitbucketdc_cloner_factory(api_session, config_path, config_dict) if ret_cloner is None and 'app-private-key' in config_dict.keys(): - ret_cloner = Cloner.using_github_app_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "app-private-key", config_dict)) - + ret_cloner = Cloner.using_github_app_auth(GithubAppAuthFactory + (CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "app-private-key", config_dict), api_session.api_endpoint)) return ret_cloner @staticmethod - def __adoe_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactory: + def __adoe_api_auth_factory(api_url : str, config_path : str, config_dict : Dict) -> AuthFactory: if CxOneFlowConfig.__has_token_auth(config_dict): return auth_basic("", CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) elif CxOneFlowConfig.__has_basic_auth(config_dict): @@ -496,7 +500,7 @@ def __adoe_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactor @staticmethod - def __bbdc_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactory: + def __bbdc_api_auth_factory(api_url : str, config_path : str, config_dict : Dict) -> AuthFactory: if CxOneFlowConfig.__has_token_auth(config_dict): return auth_bearer(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) elif CxOneFlowConfig.__has_basic_auth(config_dict): @@ -506,14 +510,15 @@ def __bbdc_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactor return None @staticmethod - def __github_api_auth_factory(config_path : str, config_dict : Dict) -> AuthFactory: + def __github_api_auth_factory(api_url : str, config_path : str, config_dict : Dict) -> AuthFactory: if CxOneFlowConfig.__has_token_auth(config_dict): return auth_bearer(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) elif CxOneFlowConfig.__has_basic_auth(config_dict): return auth_basic(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict)) elif 'app-private-key' in config_dict.keys() and config_dict['app-private-key'] is not None: - return GithubAppAuthFactory(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "app-private-key", config_dict)) + return GithubAppAuthFactory(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "app-private-key", config_dict), + api_url) return None diff --git a/orchestration/adoe.py b/orchestration/adoe.py index 4c7603b..af9cff4 100644 --- a/orchestration/adoe.py +++ b/orchestration/adoe.py @@ -2,9 +2,8 @@ import json, base64, urllib, urllib.parse from jsonpath_ng import parse from cxone_api.util import CloneUrlParser -import logging from cxone_service import CxOneService -from scm_services import SCMService, Cloner +from scm_services import SCMService from workflows.state_service import WorkflowStateService from pathlib import Path from cxone_api.scanning import ScanInspector diff --git a/orchestration/base.py b/orchestration/base.py index 252a4e2..20d0eec 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -5,7 +5,8 @@ from .exceptions import OrchestrationException from cxone_service import CxOneService from cxone_api.scanning import ScanInspector -from scm_services import SCMService, Cloner +from scm_services import SCMService +from scm_services.cloner import Cloner, CloneWorker from workflows.state_service import WorkflowStateService from workflows.messaging import PRDetails @@ -63,6 +64,9 @@ def get_header_key_safe(self, key): async def execute(self, cxone_service: CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): raise NotImplementedError("execute") + async def _get_clone_worker(self, scm_service : SCMService, clone_url : str) -> CloneWorker: + return await scm_service.cloner.clone(clone_url) + async def __exec_scan(self, cxone_service : CxOneService, scm_service : SCMService, tags) -> ScanInspector: protected_branches = await self._get_protected_branches(scm_service) @@ -79,7 +83,7 @@ async def __exec_scan(self, cxone_service : CxOneService, scm_service : SCMServi check = perf_counter_ns() OrchestratorBase.log().debug("Starting clone...") - async with await scm_service.cloner.clone(clone_url) as clone_worker: + async with await (await self._get_clone_worker(scm_service, clone_url)) as clone_worker: code_path = await clone_worker.loc() await scm_service.cloner.reset_head(code_path, source_hash) diff --git a/orchestration/bbdc.py b/orchestration/bbdc.py index e5b7dd1..46cd7ee 100644 --- a/orchestration/bbdc.py +++ b/orchestration/bbdc.py @@ -3,9 +3,9 @@ from api_utils import signature from jsonpath_ng import parse from .exceptions import OrchestrationException -import logging from cxone_service import CxOneService -from scm_services import SCMService, Cloner +from scm_services import SCMService +from scm_services.cloner import Cloner from workflows.state_service import WorkflowStateService from cxone_api.scanning import ScanInspector diff --git a/orchestration/gh.py b/orchestration/gh.py index 627eb43..f221ab7 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -5,7 +5,8 @@ import json from cxone_service import CxOneService from cxone_api.util import json_on_ok -from scm_services import SCMService, Cloner +from scm_services import SCMService +from scm_services.cloner import CloneWorker from workflows.state_service import WorkflowStateService from requests import Response @@ -144,6 +145,8 @@ async def execute(self, cxone_service : CxOneService, scm_service : SCMService, else: return await GithubOrchestrator.__workflow_map[self.__event](self, cxone_service, scm_service, workflow_service) + async def _get_clone_worker(self, scm_service : SCMService, clone_url : str) -> CloneWorker: + return scm_service.cloner.clone(clone_url, self.__json) async def _get_target_branch_and_hash(self) -> tuple: return self.__target_branch, self.__target_hash diff --git a/scm_services/__init__.py b/scm_services/__init__.py index 2dbdb0a..cf78bec 100644 --- a/scm_services/__init__.py +++ b/scm_services/__init__.py @@ -1,4 +1,3 @@ -from .cloner import Cloner from .scm import SCMService from .adoe import ADOEService from .bbdc import BBDCService diff --git a/scm_services/cloner.py b/scm_services/cloner.py index 8244bc3..d427862 100644 --- a/scm_services/cloner.py +++ b/scm_services/cloner.py @@ -4,8 +4,31 @@ from typing import Dict, List from api_utils.auth_factories import GithubAppAuthFactory -class Cloner: +class CloneWorker: + def __init__(self, clone_thread, clone_dest_path): + self.__log = logging.getLogger(f"CloneWorker:{clone_dest_path}") + self.__clone_out_tempdir = clone_dest_path + self.__clone_thread = clone_thread + + + async def loc(self) -> str: + try: + completed = await self.__clone_thread + self.__log.debug(f"Clone task: return code [{completed.returncode}] stdout: [{completed.stdout}] stderr: [{completed.stderr}]") + return self.__clone_out_tempdir.name + except subprocess.CalledProcessError as ex: + self.__log.error(f"{ex} stdout: [{ex.stdout.decode('UTF-8')}] stderr: [{ex.stderr.decode('UTF-8')})]") + raise + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + self.__log.debug(f"Cleanup: {self.__clone_out_tempdir}") + self.__clone_out_tempdir.cleanup() + +class Cloner: __https_matcher = re.compile("^http(s)?") __ssh_matcher = re.compile("^ssh") @@ -20,7 +43,7 @@ def log(clazz) -> logging.Logger: return logging.getLogger(clazz.__name__) @staticmethod - def __insert_creds_in_url(url : str, username : str, password : str) -> str: + def insert_creds_in_url(url : str, username : str, password : str) -> str: split = urllib.parse.urlsplit(url) new_netloc = f"{urllib.parse.quote(username, safe='') if username is not None else 'git'}:{SecretRegistry.register(urllib.parse.quote(password, safe=''))}@{split.netloc}" return urllib.parse.urlunsplit((split.scheme, new_netloc, split.path, split.query, split.fragment)) @@ -29,20 +52,18 @@ def __insert_creds_in_url(url : str, username : str, password : str) -> str: def using_basic_auth(username : str, password : str, in_header : bool=False): Cloner.log().debug("Clone config: using_basic_auth") - retval = Cloner() - retval.__protocol_matcher = Cloner.__https_matcher - retval.__supported_protocols = Cloner.__http_protocols - retval.__port = None - retval.__username = username - retval.__password = SecretRegistry.register(password) if not in_header: + retval = BasicAuthWithCredsInUrl(username, SecretRegistry.register(password)) retval.__clone_cmd_stub = ["git", "clone"] - retval.__fix_clone_url = lambda url: Cloner.__insert_creds_in_url(url, username, password) else: + retval = Cloner() encoded_creds = SecretRegistry.register(base64.b64encode(f"{username}:{password}".encode('UTF8')).decode('UTF8')) retval.__clone_cmd_stub = ["git", "clone", "-c", f"http.extraHeader=Authorization: Basic {encoded_creds}"] - retval.__fix_clone_url = lambda url: url + + retval.__protocol_matcher = Cloner.__https_matcher + retval.__supported_protocols = Cloner.__http_protocols + retval.__port = None return retval @@ -54,11 +75,8 @@ def using_token_auth(token : str): retval.__protocol_matcher = Cloner.__https_matcher retval.__supported_protocols = Cloner.__http_protocols retval.__port = None - retval.__clone_cmd_stub = ["git", "clone", "-c", f"http.extraHeader=Authorization: Bearer {token}"] - retval.__fix_clone_url = lambda url: url - return retval @staticmethod @@ -77,20 +95,17 @@ def using_ssh_auth(ssh_private_key_file : Path, ssh_port : int): retval.__env['GIT_SSH_COMMAND'] = f"ssh -i '{shlex.quote(retval.__keyfile)}' -oIdentitiesOnly=yes -oStrictHostKeyChecking=accept-new -oHostKeyAlgorithms=+ssh-rsa -oPubkeyAcceptedAlgorithms=+ssh-rsa" retval.__clone_cmd_stub = ["git", "clone"] - retval.__fix_clone_url = lambda url: url - return retval @staticmethod - def using_github_app_auth(app_private_key : str): + def using_github_app_auth(gh_auth_factory : GithubAppAuthFactory): Cloner.log().debug("Clone config: using_github_app_auth") - retval = GithubAppCloner(GithubAppAuthFactory(app_private_key)) + retval = GithubAppCloner(gh_auth_factory) retval.__protocol_matcher = Cloner.__https_matcher retval.__supported_protocols = Cloner.__http_protocols retval.__port = None - retval.__fix_clone_url = lambda url: url - # retval.__clone_cmd_stub = ["git", "clone", "-c"] + retval.__clone_cmd_stub = ["git", "clone"] return retval @@ -100,6 +115,9 @@ def select_protocol_from_supported(self, protocol_list): return x return None + async def _fix_clone_url(self, clone_url : str, event_context : Dict=None, force_reauth : bool=False): + return clone_url + @property def supported_protocols(self): return self.__supported_protocols @@ -111,16 +129,16 @@ def destination_port(self): async def _get_clone_cmd_stub(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> List: return self.__clone_cmd_stub - async def clone(self, clone_url, event_context : Dict=None, api_url : str=None, force_reauth : bool=False): + async def clone(self, clone_url, event_context : Dict=None, force_reauth : bool=False): Cloner.log().debug(f"Clone Execution for: {clone_url}") - fixed_clone_url = self.__fix_clone_url(clone_url) + fixed_clone_url = await self._fix_clone_url(clone_url, event_context, force_reauth) clone_output_loc = tempfile.TemporaryDirectory(delete=False) - cmd = await self._get_clone_cmd_stub(event_context, api_url, force_reauth) + [fixed_clone_url, clone_output_loc.name] + cmd = await self._get_clone_cmd_stub(event_context, force_reauth) + [fixed_clone_url, clone_output_loc.name] Cloner.log().debug(cmd) thread = asyncio.to_thread(subprocess.run, cmd, capture_output=True, env=self.__env, check=True) - return Cloner.__clone_worker(thread, clone_output_loc) + return CloneWorker(thread, clone_output_loc) async def reset_head(self, code_path, hash): try: @@ -133,37 +151,21 @@ async def reset_head(self, code_path, hash): self.log().error(f"{ex} stdout: [{ex.stdout.decode('UTF-8')}] stderr: [{ex.stderr.decode('UTF-8')})]") raise - class __clone_worker: - - def __init__(self, clone_thread, clone_dest_path): - self.__log = logging.getLogger(f"__clone_worker:{clone_dest_path}") - self.__clone_out_tempdir = clone_dest_path - self.__clone_thread = clone_thread - - - async def loc(self) -> str: - try: - completed = await self.__clone_thread - self.__log.debug(f"Clone task: return code [{completed.returncode}] stdout: [{completed.stdout}] stderr: [{completed.stderr}]") - return self.__clone_out_tempdir.name - except subprocess.CalledProcessError as ex: - self.__log.error(f"{ex} stdout: [{ex.stdout.decode('UTF-8')}] stderr: [{ex.stderr.decode('UTF-8')})]") - raise +class BasicAuthWithCredsInUrl(Cloner): + def __init__(self, username : str, password : str): + Cloner.__init__(self) + self.__username = username + self.__password = password - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc, tb): - self.__log.debug(f"Cleanup: {self.__clone_out_tempdir}") - self.__clone_out_tempdir.cleanup() + async def _fix_clone_url(self, clone_url : str, event_context : Dict=None, force_reauth : bool=False): + return Cloner.insert_creds_in_url(clone_url, self.__username, self.__password) class GithubAppCloner(Cloner): def __init__(self, auth_factory : GithubAppAuthFactory): + Cloner.__init__(self) self.__auth_factory = auth_factory - async def _get_clone_cmd_stub(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> List: - - token = await self.__auth_factory.get_token(event_context, api_url, force_reauth) - return ["git", "clone", "-c", - f"http.extraHeader=Authorization: Bearer {token}"] + async def _fix_clone_url(self, clone_url : str, event_context : Dict=None, force_reauth : bool=False): + token = SecretRegistry.register(await self.__auth_factory.get_token(event_context, force_reauth)) + return Cloner.insert_creds_in_url(clone_url, "x-access-token", token) diff --git a/wsgi.py b/wsgi.py index c4cdff9..a7fc749 100644 --- a/wsgi.py +++ b/wsgi.py @@ -8,7 +8,7 @@ from flask import Flask, request, Response, send_from_directory from orchestration import OrchestrationDispatch, BitBucketDataCenterOrchestrator, \ AzureDevOpsEnterpriseOrchestrator, GithubOrchestrator -import json, logging, asyncio, os +import json, logging, os from config import CxOneFlowConfig, ConfigurationException, get_config_path from time import perf_counter_ns from task_management import TaskManager From 572b58f45047e63b932d19bea6505f4aafb76367 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Mon, 23 Sep 2024 20:44:52 +0000 Subject: [PATCH 13/35] add debug logging --- api_utils/auth_factories.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index e85c808..67c6439 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -75,13 +75,16 @@ async def __get_token_tuple(self, event_context : Dict=None, force_reauth : bool token_tuple = tuple(GithubAppAuthFactory.__token_cache[install_id]) exp = token_tuple[1] if datetime.now(exp.tzinfo) >= exp: + GithubAppAuthFactory.log().debug(f"Token for app_id {app_id} install_id {install_id} expired at {exp}") token_tuple = None if token_tuple is None or force_reauth: + GithubAppAuthFactory.log().debug(f"Generating app token for app_id {app_id} install_id {install_id}") token_response = json_on_ok(await asyncio.to_thread(request, method="POST", url=f"{self.__api_url.rstrip("/")}/app/installations/{install_id}/access_tokens", headers = {"User-Agent" : __agent__}, auth=HTTPBearerAuth(self.__encoded_jwt_factory(app_id)))) + GithubAppAuthFactory.log().debug(f"App token for app_id {app_id} install_id {install_id} generated.") token_tuple = (SecretRegistry.register(token_response['token']), datetime.fromisoformat(token_response['expires_at'])) From 0a1956b132a35c41a2bf9e9dadd0030fba960a8e Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Mon, 23 Sep 2024 21:27:48 +0000 Subject: [PATCH 14/35] pr wip --- orchestration/gh.py | 69 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/orchestration/gh.py b/orchestration/gh.py index f221ab7..16f2596 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -9,12 +9,13 @@ from scm_services.cloner import CloneWorker from workflows.state_service import WorkflowStateService from requests import Response +from cxone_api.scanning import ScanInspector + class GithubOrchestrator(OrchestratorBase): __api_page_max = 100 - __install_action_query = parse("$.action") __install_sender_query = parse("$.sender.login") __install_target_query = parse("$.installation.account.login") @@ -24,16 +25,25 @@ class GithubOrchestrator(OrchestratorBase): __install_permissions_query = parse("$.installation.permissions") __install_route_url_query = parse("$.installation.account.html_url") - __push_route_url_query = parse("$.repository[clone_url,ssh_url]") - __push_ssh_clone_url_query = parse("$.repository.ssh_url") - __push_http_clone_url_query = parse("$.repository.clone_url") + __push_target_branch_query = parse("$.ref") __push_target_hash_query = parse("$.head_commit.id") __push_project_key_query = parse("$.repository.name") __push_org_key_query = parse("$.repository.owner.name") + + __pull_target_branch_query = parse("$.pull_request.base.ref") + __pull_target_hash_query = parse("$.pull_request.base.sha") + __pull_source_branch_query = parse("$.pull_request.head.ref") + __pull_source_hash_query = parse("$.pull_request.head.sha") + __pull_pr_id_query = parse("$.pull_request.id") + + __code_event_route_url_query = parse("$.repository[clone_url,ssh_url]") + __code_event_ssh_clone_url_query = parse("$.repository.ssh_url") + __code_event_http_clone_url_query = parse("$.repository.clone_url") + __code_event_default_branch_name_extract = parse("$.repository.default_branch") + __branch_names_extract = parse("$.[*].name") - __default_branch_name_extract = parse("$.default_branch") __expected_events = ['pull_request', 'pull_request_review', 'push'] __expected_permissions = { @@ -110,13 +120,13 @@ async def __log_app_install(self, cxone_service : CxOneService, scm_service : SC def __installation_route_urls(self): return [GithubOrchestrator.__install_route_url_query.find(self.__json)[0].value] - def __push_route_urls(self): - return [GithubOrchestrator.__push_route_url_query.find(self.__json)[0].value] - - def __push_clone_urls(self): + def __code_event_route_urls(self): + return [GithubOrchestrator.__code_event_route_url_query.find(self.__json)[0].value] + + def __code_event_clone_urls(self): return { - "ssh" : GithubOrchestrator.__push_ssh_clone_url_query.find(self.__json)[0].value, - "http" : GithubOrchestrator.__push_http_clone_url_query.find(self.__json)[0].value + "ssh" : GithubOrchestrator.__code_event_ssh_clone_url_query.find(self.__json)[0].value, + "http" : GithubOrchestrator.__code_event_http_clone_url_query.find(self.__json)[0].value } def __init__(self, headers : dict, webhook_payload : dict): @@ -178,6 +188,30 @@ async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_se return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) + async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService) -> ScanInspector: + self.__target_branch = GithubOrchestrator.__pull_target_branch_query.find(self.__json)[0].value + self.__source_branch = GithubOrchestrator.__pull_source_branch_query.find(self.__json)[0].value + + self.__target_hash = GithubOrchestrator.__pull_target_hash_query.find(self.__json)[0].value + self.__source_hash = GithubOrchestrator.__pull_source_hash_query.find(self.__json)[0].value + + self.__pr_id = GithubOrchestrator.__pull_pr_id_query.find(self.__json)[0].value + + return await OrchestratorBase._execute_pr_scan_workflow(self, cxone_service, scm_service, workflow_service) + + + @property + def _pr_id(self) -> str: + return self.__pr_id + + @property + def _pr_state(self) -> str: + raise NotImplementedError("_pr_state") + + @property + def _pr_status(self) -> str: + raise NotImplementedError("_pr_status") + async def _get_protected_branches(self, scm_service : SCMService) -> list: ret_branches = [] @@ -201,8 +235,7 @@ def args_gen(offset : int): ret_branches.append(branch) if len(ret_branches) == 0: - ret_branches.append(GithubOrchestrator.__default_branch_name_extract.find( - json_on_ok(await scm_service.exec("GET", f"/repos/{self._repo_organization}/{self._repo_project_key}", event_msg=self.__json)))[0].value) + ret_branches.append(GithubOrchestrator.__code_event_default_branch_name_extract.find(self.__json)[0].value) return ret_branches @@ -234,15 +267,19 @@ async def get_cxone_project_name(self) -> str: __workflow_map = { "installation" : __log_app_install, "installation_repositories" : __log_app_install, - "push" : _execute_push_scan_workflow + "push" : _execute_push_scan_workflow, + "pull_request" : _execute_pr_scan_workflow } __route_url_parser_dispatch_map = { "installation" : __installation_route_urls, "installation_repositories" : __installation_route_urls, - "push" : __push_route_urls + "push" : __code_event_route_urls, + "pull_request" : __code_event_route_urls + } __clone_url_parser_dispatch_map = { - "push" : __push_clone_urls + "push" : __code_event_clone_urls, + "pull_request" : __code_event_clone_urls } From 2bb8d4bfcca7949a05a80c130ba51f9e649f9c05 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 24 Sep 2024 17:25:16 +0000 Subject: [PATCH 15/35] log stack trace --- task_management/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/task_management/__init__.py b/task_management/__init__.py index 6dbf4f3..345c2ac 100644 --- a/task_management/__init__.py +++ b/task_management/__init__.py @@ -1,5 +1,6 @@ import asyncio, logging, time from threading import Thread, Lock +from traceback import TracebackException class TaskManager: @@ -43,6 +44,7 @@ def __callback(future): def __log_future_result(future): if future.exception() is not None: TaskManager.log().exception(future.exception()) + TaskManager.log().error("".join(TracebackException.from_exception(future.exception()).format())) else: if future.result() is not None: TaskManager.log().debug(future.result()) From 256264cf255105f4cae5cc94b421e866a3e97de7 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 24 Sep 2024 17:25:30 +0000 Subject: [PATCH 16/35] pr scans working --- orchestration/base.py | 14 ++++++---- orchestration/gh.py | 61 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/orchestration/base.py b/orchestration/base.py index 20d0eec..ae61e8d 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -152,11 +152,15 @@ async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_serv } inspector = await self.__exec_scan(cxone_service, scm_service, scan_tags) - await workflow_service.start_pr_scan_workflow(inspector.project_id, inspector.scan_id, - PRDetails(clone_url=self._repo_clone_url(scm_service.cloner), - repo_project=self._repo_project_key, repo_slug=self._repo_slug, - organization=self._repo_organization, pr_id=self._pr_id, - source_branch=source_branch, target_branch=target_branch)) + if inspector is not None: + await workflow_service.start_pr_scan_workflow(inspector.project_id, inspector.scan_id, + PRDetails(clone_url=self._repo_clone_url(scm_service.cloner), + repo_project=self._repo_project_key, repo_slug=self._repo_slug, + organization=self._repo_organization, pr_id=self._pr_id, + source_branch=source_branch, target_branch=target_branch)) + else: + OrchestratorBase.log().warning(f"No scan returned, PR workflow not started for PR {self._pr_id}.") + return inspector async def _execute_pr_tag_update_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): diff --git a/orchestration/gh.py b/orchestration/gh.py index 16f2596..bc8eb87 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -16,7 +16,8 @@ class GithubOrchestrator(OrchestratorBase): __api_page_max = 100 - __install_action_query = parse("$.action") + __event_action_query = parse("$.action") + __install_sender_query = parse("$.sender.login") __install_target_query = parse("$.installation.account.login") __install_target_type_query = parse("$.installation.account.type") @@ -36,7 +37,12 @@ class GithubOrchestrator(OrchestratorBase): __pull_target_hash_query = parse("$.pull_request.base.sha") __pull_source_branch_query = parse("$.pull_request.head.ref") __pull_source_hash_query = parse("$.pull_request.head.sha") - __pull_pr_id_query = parse("$.pull_request.id") + __pull_id_query = parse("$.pull_request.id") + __pull_state_query = parse("$.pull_request.state") + __pull_draft_query = parse("$.pull_request.draft") + __pull_html_url = parse("$.pull_request.html_url") + __pull_project_key_query = parse("$.repository.name") + __pull_org_key_query = parse("$.repository.owner.login") __code_event_route_url_query = parse("$.repository[clone_url,ssh_url]") __code_event_ssh_clone_url_query = parse("$.repository.ssh_url") @@ -67,7 +73,7 @@ def is_diagnostic(self) -> bool: return self.__isdiagnostic async def __log_app_install(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): - action = GithubOrchestrator.__install_action_query.find(self.__json)[0].value + action = GithubOrchestrator.__event_action_query.find(self.__json)[0].value sender = GithubOrchestrator.__install_sender_query.find(self.__json)[0].value target = GithubOrchestrator.__install_target_query.find(self.__json)[0].value target_type = GithubOrchestrator.__install_target_type_query.find(self.__json)[0].value @@ -185,32 +191,60 @@ async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_se self.__target_branch = self.__source_branch = OrchestratorBase.normalize_branch_name( GithubOrchestrator.__push_target_branch_query.find(self.__json)[0].value) self.__target_hash = self.__source_hash = GithubOrchestrator.__push_target_hash_query.find(self.__json)[0].value - + + self.__project_key = GithubOrchestrator.__push_project_key_query.find(self.__json)[0].value + self.__org = GithubOrchestrator.__push_org_key_query.find(self.__json)[0].value + return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService) -> ScanInspector: + + action = GithubOrchestrator.__event_action_query.find(self.__json)[0].value + html_url = GithubOrchestrator.__pull_html_url.find(self.__json)[0].value + self.__pr_id = GithubOrchestrator.__pull_id_query.find(self.__json)[0].value + self.__project_key = GithubOrchestrator.__pull_project_key_query.find(self.__json)[0].value + self.__org = GithubOrchestrator.__pull_org_key_query.find(self.__json)[0].value + + if bool(GithubOrchestrator.__pull_draft_query.find(self.__json)[0].value): + GithubOrchestrator.log().info(f"Skipping draft PR {self.__pr_id}: {html_url}") + return + + if action not in GithubOrchestrator.__pr_scan_actions: + GithubOrchestrator.log().info(f"PR {self.__pr_id} with action [{action}] skipped: {html_url}") + return + self.__target_branch = GithubOrchestrator.__pull_target_branch_query.find(self.__json)[0].value self.__source_branch = GithubOrchestrator.__pull_source_branch_query.find(self.__json)[0].value self.__target_hash = GithubOrchestrator.__pull_target_hash_query.find(self.__json)[0].value self.__source_hash = GithubOrchestrator.__pull_source_hash_query.find(self.__json)[0].value - self.__pr_id = GithubOrchestrator.__pull_pr_id_query.find(self.__json)[0].value + self.__pr_state = GithubOrchestrator.__pull_state_query.find(self.__json)[0].value + + # TODO: Set status with reviewer names + # $.pull_request.assignee - can be null + # $.pull_request.assignees - can be an empty list + # $.pull_request[requested_teams,requested_reviewers][*] - can have no match + # Can be: + # NO_REVIEWERS + # REVIEWERS_REQUESTED + # REVIEWERS_ASSIGNED + self.__pr_status = "NO_REVIEWERS" return await OrchestratorBase._execute_pr_scan_workflow(self, cxone_service, scm_service, workflow_service) @property def _pr_id(self) -> str: - return self.__pr_id + return str(self.__pr_id) @property def _pr_state(self) -> str: - raise NotImplementedError("_pr_state") + return self.__pr_state @property def _pr_status(self) -> str: - raise NotImplementedError("_pr_status") + return self.__pr_status async def _get_protected_branches(self, scm_service : SCMService) -> list: @@ -243,11 +277,11 @@ def args_gen(offset : int): @property def _repo_project_key(self) -> str: - return GithubOrchestrator.__push_project_key_query.find(self.__json)[0].value + return self.__project_key @property def _repo_organization(self) -> str: - return GithubOrchestrator.__push_org_key_query.find(self.__json)[0].value + return self.__org @property def _repo_slug(self) -> str: @@ -283,3 +317,10 @@ async def get_cxone_project_name(self) -> str: "push" : __code_event_clone_urls, "pull_request" : __code_event_clone_urls } + + __pr_scan_actions = [ + "opened", + "synchronize", + "ready_for_review", + "reopened" + ] From 49ce34fb23aea2aa90c5ee19e25b7494dec8b21e Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 24 Sep 2024 19:26:09 +0000 Subject: [PATCH 17/35] tag updates impl --- orchestration/gh.py | 113 +++++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 38 deletions(-) diff --git a/orchestration/gh.py b/orchestration/gh.py index bc8eb87..1960af5 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -43,6 +43,9 @@ class GithubOrchestrator(OrchestratorBase): __pull_html_url = parse("$.pull_request.html_url") __pull_project_key_query = parse("$.repository.name") __pull_org_key_query = parse("$.repository.owner.login") + __pull_assignee_query = parse("$.pull_request.assignee") + __pull_assignees_query = parse("$.pull_request.assignees") + __pull_requested_reviewers_query = parse("$.pull_request[requested_teams,requested_reviewers][*]") __code_event_route_url_query = parse("$.repository[clone_url,ssh_url]") __code_event_ssh_clone_url_query = parse("$.repository.ssh_url") @@ -73,13 +76,12 @@ def is_diagnostic(self) -> bool: return self.__isdiagnostic async def __log_app_install(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): - action = GithubOrchestrator.__event_action_query.find(self.__json)[0].value sender = GithubOrchestrator.__install_sender_query.find(self.__json)[0].value target = GithubOrchestrator.__install_target_query.find(self.__json)[0].value target_type = GithubOrchestrator.__install_target_type_query.find(self.__json)[0].value - GithubOrchestrator.log().info(f"Install event '{action}': Initiated by [{sender}] on {target_type} [{target}]") - if action in ["created", "new_permissions_accepted", "added"]: + GithubOrchestrator.log().info(f"Install event '{self.__action}': Initiated by [{sender}] on {target_type} [{target}]") + if self.__action in ["created", "new_permissions_accepted", "added"]: warned = False bad = False if not target_type == "Organization": @@ -147,6 +149,14 @@ def __init__(self, headers : dict, webhook_payload : dict): return self.__json = json.loads(webhook_payload) + + action_found = GithubOrchestrator.__event_action_query.find(self.__json) + if len(action_found) > 0: + self.__action = action_found[0].value + self.__dispatch_event = f"{self.__event}:{self.__action if self.__action is not None else ""}" + else: + self.__dispatch_event = self.__event + self.__route_urls = GithubOrchestrator.__route_url_parser_dispatch_map[self.__event](self) \ if self.__event in GithubOrchestrator.__route_url_parser_dispatch_map.keys() else [] @@ -156,10 +166,10 @@ def __init__(self, headers : dict, webhook_payload : dict): async def execute(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): - if self.__event not in GithubOrchestrator.__workflow_map.keys(): - GithubOrchestrator.log().error(f"Unhandled event type: {self.__event}") + if self.__dispatch_event not in GithubOrchestrator.__workflow_map.keys(): + GithubOrchestrator.log().error(f"Unhandled event type: {self.__dispatch_event}") else: - return await GithubOrchestrator.__workflow_map[self.__event](self, cxone_service, scm_service, workflow_service) + return await GithubOrchestrator.__workflow_map[self.__dispatch_event](self, cxone_service, scm_service, workflow_service) async def _get_clone_worker(self, scm_service : SCMService, clone_url : str) -> CloneWorker: return scm_service.cloner.clone(clone_url, self.__json) @@ -197,42 +207,59 @@ async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_se return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) - async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService) -> ScanInspector: - action = GithubOrchestrator.__event_action_query.find(self.__json)[0].value - html_url = GithubOrchestrator.__pull_html_url.find(self.__json)[0].value - self.__pr_id = GithubOrchestrator.__pull_id_query.find(self.__json)[0].value - self.__project_key = GithubOrchestrator.__pull_project_key_query.find(self.__json)[0].value - self.__org = GithubOrchestrator.__pull_org_key_query.find(self.__json)[0].value + def __get_pr_assignees(self): + ret = [] + assignee = GithubOrchestrator.__pull_assignee_query.find(self.__json) + assignees = GithubOrchestrator.__pull_assignees_query.find(self.__json) - if bool(GithubOrchestrator.__pull_draft_query.find(self.__json)[0].value): - GithubOrchestrator.log().info(f"Skipping draft PR {self.__pr_id}: {html_url}") - return + if len(assignees) > 0 and assignees[0].value is not None and len(assignees[0].value) > 0: + ret = assignees[0].value + + if len(assignee) > 0 and assignee[0].value is not None: + ret.append(assignee[0].value) + + return ret + + def __get_pr_reviewers(self): + reviewers = GithubOrchestrator.__pull_requested_reviewers_query.find(self.__json) + if len(reviewers) > 0 and reviewers[0].value is not None: + return reviewers[0].value - if action not in GithubOrchestrator.__pr_scan_actions: - GithubOrchestrator.log().info(f"PR {self.__pr_id} with action [{action}] skipped: {html_url}") - return + return [] + def __populate_common_pr_data(self): + self.__pr_html_url = GithubOrchestrator.__pull_html_url.find(self.__json)[0].value + self.__pr_id = GithubOrchestrator.__pull_id_query.find(self.__json)[0].value + self.__project_key = GithubOrchestrator.__pull_project_key_query.find(self.__json)[0].value + self.__org = GithubOrchestrator.__pull_org_key_query.find(self.__json)[0].value self.__target_branch = GithubOrchestrator.__pull_target_branch_query.find(self.__json)[0].value self.__source_branch = GithubOrchestrator.__pull_source_branch_query.find(self.__json)[0].value - self.__target_hash = GithubOrchestrator.__pull_target_hash_query.find(self.__json)[0].value self.__source_hash = GithubOrchestrator.__pull_source_hash_query.find(self.__json)[0].value + self.__is_draft = bool(GithubOrchestrator.__pull_draft_query.find(self.__json)[0].value) + self.__pr_state = f"{GithubOrchestrator.__pull_state_query.find(self.__json)[0].value}{"-draft" if self.__is_draft else ""}" - self.__pr_state = GithubOrchestrator.__pull_state_query.find(self.__json)[0].value + if len(self.__get_pr_assignees()) > 0: + self.__pr_status = "REVIEWERS_ASSIGNED" + elif len(self.__get_pr_reviewers()) > 0: + self.__pr_status = "REVIEWERS_REQUESTED" + else: + self.__pr_status = "NO_REVIEWERS" - # TODO: Set status with reviewer names - # $.pull_request.assignee - can be null - # $.pull_request.assignees - can be an empty list - # $.pull_request[requested_teams,requested_reviewers][*] - can have no match - # Can be: - # NO_REVIEWERS - # REVIEWERS_REQUESTED - # REVIEWERS_ASSIGNED - self.__pr_status = "NO_REVIEWERS" + async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService) -> ScanInspector: + self.__populate_common_pr_data() + + if self.__is_draft: + GithubOrchestrator.log().info(f"Skipping draft PR {self.__pr_id}: {self.__pr_html_url}") + return + return await OrchestratorBase._execute_pr_scan_workflow(self, cxone_service, scm_service, workflow_service) + async def _execute_pr_tag_update_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): + self.__populate_common_pr_data() + return await OrchestratorBase._execute_pr_tag_update_workflow(self, cxone_service, scm_service, workflow_service) @property def _pr_id(self) -> str: @@ -299,10 +326,26 @@ async def get_cxone_project_name(self) -> str: __workflow_map = { - "installation" : __log_app_install, - "installation_repositories" : __log_app_install, + "installation:deleted" : __log_app_install, + "installation:unsuspend" : __log_app_install, + "installation:suspend" : __log_app_install, + "installation:created" : __log_app_install, + "installation_repositories:deleted" : __log_app_install, + "installation_repositories:unsuspend" : __log_app_install, + "installation_repositories:suspend" : __log_app_install, + "installation_repositories:created" : __log_app_install, "push" : _execute_push_scan_workflow, - "pull_request" : _execute_pr_scan_workflow + "pull_request:opened" : _execute_pr_scan_workflow, + "pull_request:synchronize" : _execute_pr_scan_workflow, + "pull_request:ready_for_review" : _execute_pr_scan_workflow, + "pull_request:reopened" : _execute_pr_scan_workflow, + "pull_request:assigned" : _execute_pr_tag_update_workflow, + "pull_request:unassigned" : _execute_pr_tag_update_workflow, + "pull_request:review_request_removed" : _execute_pr_tag_update_workflow, + "pull_request:review_requested" : _execute_pr_tag_update_workflow, + "pull_request:closed" : _execute_pr_tag_update_workflow, + "pull_request:converted_to_draft" : _execute_pr_tag_update_workflow + } __route_url_parser_dispatch_map = { @@ -318,9 +361,3 @@ async def get_cxone_project_name(self) -> str: "pull_request" : __code_event_clone_urls } - __pr_scan_actions = [ - "opened", - "synchronize", - "ready_for_review", - "reopened" - ] From ecda0c4d3e09bdbdf86d467d9b19e3a6ea0dbfa5 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 24 Sep 2024 20:13:43 +0000 Subject: [PATCH 18/35] prep for pr feedback --- scm_services/gh.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scm_services/gh.py b/scm_services/gh.py index 869fbd0..f5e12e9 100644 --- a/scm_services/gh.py +++ b/scm_services/gh.py @@ -1,7 +1,14 @@ from .scm import SCMService -# from api_utils.apisession import APISession from scm_services.cloner import Cloner +from typing import Dict class GHService(SCMService): - pass + __max_content_chars = 65535 + + async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, + summary_markdown : str, event_msg : Dict=None): + raise NotImplementedError("exec_pr_decorate") + + def create_code_permalink(self, organization : str, project : str, repo_slug : str, branch : str, code_path : str, code_line : str): + raise NotImplementedError("create_code_permalink") From a7106e9856eff29cf6bb9ee706f358db616d87aa Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Wed, 25 Sep 2024 16:09:16 +0000 Subject: [PATCH 19/35] add headers to event context --- api_utils/apisession.py | 5 ++- api_utils/auth_factories.py | 54 +++++++++++++++++++------- orchestration/adoe.py | 47 +++++++++++------------ orchestration/base.py | 17 ++++----- orchestration/bbdc.py | 50 ++++++++++++------------ orchestration/gh.py | 76 ++++++++++++++++++------------------- scm_services/cloner.py | 9 +++-- scm_services/scm.py | 5 ++- wsgi.py | 7 ++-- 9 files changed, 146 insertions(+), 124 deletions(-) diff --git a/api_utils/apisession.py b/api_utils/apisession.py index 47c1c5f..4faeb17 100644 --- a/api_utils/apisession.py +++ b/api_utils/apisession.py @@ -4,6 +4,7 @@ from typing import Dict, Union, Any import urllib, logging, sys, asyncio from api_utils import AuthFactory +from api_utils.auth_factories import EventContext class SCMAuthException(Exception): pass @@ -49,7 +50,7 @@ def _form_url(self, url_path, anchor=None, **kwargs): return f"{base}/{suffix}{"?" if len(args) > 0 else ""}{"&".join(args)}{f"#{anchor}" if anchor is not None else ""}" - async def exec(self, event_msg : Dict, method : str, path : str, query : Dict = None, body : Any = None, extra_headers : Dict = None) -> Response: + async def exec(self, event_context : EventContext, method : str, path : str, query : Dict = None, body : Any = None, extra_headers : Dict = None) -> Response: url = self._form_url(path) headers = dict(self.__headers) if not extra_headers is None: @@ -61,7 +62,7 @@ async def exec(self, event_msg : Dict, method : str, path : str, query : Dict = APISession.log().debug(f"Executing: {prepStr} #{tryCount}") response = await asyncio.to_thread(request, method=method, url=url, params=query, - data=body, headers=headers, auth=await self.__auth_factory.get_auth(event_msg, tryCount > 0), + data=body, headers=headers, auth=await self.__auth_factory.get_auth(event_context, tryCount > 0), timeout=self.__timeout, proxies=self.__proxies, verify=self.__verify) logStr = f"{response.status_code}: {response.reason} {prepStr}" diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index 67c6439..07a4fc1 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -2,7 +2,7 @@ from requests import request from typing import Dict from jsonpath_ng import parse -import jwt, time, asyncio, logging +import jwt, time, asyncio, logging, json, re from datetime import datetime from threading import Lock from _agent import __agent__ @@ -13,15 +13,43 @@ class AuthFactoryException(BaseException): pass +class EventContext: + def __init__(self, event_payload : Dict, event_headers : Dict): + self.__payload = event_payload + self.__msg = json.loads(event_payload) + self.__headers = event_headers + + @property + def message(self) -> Dict: + return self.__msg + + @property + def raw_event_payload(self): + return self.__payload + + + @property + def headers(self) -> Dict: + return self.__headers + + +class HeaderFilteredEventContext(EventContext): + def __init__(self, event_payload : Dict, event_headers : Dict, header_key_regex : str): + pattern = re.compile(header_key_regex) + EventContext.__init__(self, event_payload, {k:v for k,v in event_headers if pattern.match(k)}) + + + + class AuthFactory: @classmethod def log(clazz): return logging.getLogger(clazz.__name__) - async def get_auth(self, event_context : Dict=None, force_reauth : bool=False) -> AuthBase: + async def get_auth(self, event_context : EventContext=None, force_reauth : bool=False) -> AuthBase: raise NotImplementedError("get_auth") - async def get_token(self, event_context : Dict=None, force_reauth : bool=False) -> str: + async def get_token(self, event_context : EventContext=None, force_reauth : bool=False) -> str: raise NotImplementedError("get_token") @@ -29,7 +57,7 @@ class StaticAuthFactory(AuthFactory): def __init__(self, static_auth : AuthBase): self.__auth = static_auth - async def get_auth(self, event_context : Dict=None, force_reauth : bool=False) -> AuthBase: + async def get_auth(self, event_context : EventContext=None, force_reauth : bool=False) -> AuthBase: return self.__auth @@ -39,7 +67,7 @@ class GithubAppAuthFactory(AuthFactory): __token_cache = {} __event_installation_id = parse("$.installation.id") - __event_app_id = parse("$.sender.id") + __app_id_header = "X-Github-Hook-Installation-Target-Id" def __init__(self, private_key : str, api_url : str): @@ -56,17 +84,17 @@ def __encoded_jwt_factory(self, install_id : int) -> str: return jwt.encode(payload, self.__pkey, algorithm='RS256') - async def __get_token_tuple(self, event_context : Dict=None, force_reauth : bool=False): + async def __get_token_tuple(self, event_context : EventContext=None, force_reauth : bool=False): - install_id_found = GithubAppAuthFactory.__event_installation_id.find(event_context) + install_id_found = GithubAppAuthFactory.__event_installation_id.find(event_context.message) if len(install_id_found) == 0: raise AuthFactoryException("GitHub installation id was not found in the event payload.") install_id = install_id_found[0].value - app_id_found = GithubAppAuthFactory.__event_app_id.find(event_context) - if len(app_id_found) == 0: - raise AuthFactoryException("GitHub app id was not found in the event payload.") - app_id = app_id_found[0].value + if GithubAppAuthFactory.__app_id_header in event_context.headers.keys(): + app_id = event_context.headers[GithubAppAuthFactory.__app_id_header] + else: + raise AuthFactoryException(f"Header {GithubAppAuthFactory.__app_id_header} not found in event context.") token_tuple = None @@ -93,12 +121,12 @@ async def __get_token_tuple(self, event_context : Dict=None, force_reauth : bool return token_tuple - async def get_auth(self, event_context : Dict=None, force_reauth : bool=False) -> AuthBase: + async def get_auth(self, event_context : EventContext=None, force_reauth : bool=False) -> AuthBase: if event_context is None: raise AuthFactoryException("Event context is required.") return HTTPBearerAuth ((await self.__get_token_tuple(event_context, force_reauth))[0]) - async def get_token(self, event_context : Dict=None, force_reauth : bool=False) -> str: + async def get_token(self, event_context : EventContext=None, force_reauth : bool=False) -> str: if event_context is None: raise AuthFactoryException("Event context is required.") return (await self.__get_token_tuple(event_context, force_reauth))[0] diff --git a/orchestration/adoe.py b/orchestration/adoe.py index af9cff4..5ed9197 100644 --- a/orchestration/adoe.py +++ b/orchestration/adoe.py @@ -1,5 +1,5 @@ from .base import OrchestratorBase -import json, base64, urllib, urllib.parse +import base64, urllib, urllib.parse from jsonpath_ng import parse from cxone_api.util import CloneUrlParser from cxone_service import CxOneService @@ -7,6 +7,7 @@ from workflows.state_service import WorkflowStateService from pathlib import Path from cxone_api.scanning import ScanInspector +from api_utils.auth_factories import EventContext class AzureDevOpsEnterpriseOrchestrator(OrchestratorBase): @@ -39,22 +40,20 @@ class AzureDevOpsEnterpriseOrchestrator(OrchestratorBase): def config_key(self): return "adoe" - def __init__(self, headers, webhook_payload): - OrchestratorBase.__init__(self, headers, webhook_payload) + def __init__(self, event_context : EventContext): + OrchestratorBase.__init__(self, event_context) - self.__json = json.loads(webhook_payload) - - self.__isdiagnostic = AzureDevOpsEnterpriseOrchestrator.__diag_id in [x.value for x in list(AzureDevOpsEnterpriseOrchestrator.__diagid_query.find(self.__json))] + self.__isdiagnostic = AzureDevOpsEnterpriseOrchestrator.__diag_id in [x.value for x in list(AzureDevOpsEnterpriseOrchestrator.__diagid_query.find(self.event_context.message))] if self.__isdiagnostic: return - self.__event = [x.value for x in list(self.__payload_type_query.find(self.__json))][0] - self.__route_urls = [x.value for x in list(self.__remoteurl_query.find(self.__json))] + self.__event = [x.value for x in list(self.__payload_type_query.find(self.event_context.message))][0] + self.__route_urls = [x.value for x in list(self.__remoteurl_query.find(self.event_context.message))] self.__clone_url = self.__route_urls[0] - self.__default_branches = [OrchestratorBase.normalize_branch_name(x.value) for x in list(self.__push_default_branch_query.find(self.__json))] - self.__repo_key = [x.value for x in list(self.__repo_project_key_query.find(self.__json))][0] - self.__repo_slug = [x.value for x in list(self.__repo_slug_query.find(self.__json))][0] - self.__collection_url = [x.value for x in list(self.__collection_url_query.find(self.__json))][0] + self.__default_branches = [OrchestratorBase.normalize_branch_name(x.value) for x in list(self.__push_default_branch_query.find(self.event_context.message))] + self.__repo_key = [x.value for x in list(self.__repo_project_key_query.find(self.event_context.message))][0] + self.__repo_slug = [x.value for x in list(self.__repo_slug_query.find(self.event_context.message))][0] + self.__collection_url = [x.value for x in list(self.__collection_url_query.find(self.event_context.message))][0] self.__collection = Path(urllib.parse.urlparse(self.__collection_url).path).name @@ -126,35 +125,35 @@ async def get_cxone_project_name(self) -> str: return f"{p.org}/{self._repo_project_key}/{self._repo_name}" async def __is_pr_draft(self) -> bool: - return bool(AzureDevOpsEnterpriseOrchestrator.__pr_draft_query.find(self.__json)[0].value) + return bool(AzureDevOpsEnterpriseOrchestrator.__pr_draft_query.find(self.event_context.message)[0].value) async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): self.__source_branch = self.__target_branch = OrchestratorBase.normalize_branch_name( - [x.value for x in list(self.__push_target_branch_query.find(self.__json))][0]) - self.__source_hash = self.__target_hash = [x.value for x in list(self.__push_target_hash_query.find(self.__json))][0] + [x.value for x in list(self.__push_target_branch_query.find(self.event_context.message))][0]) + self.__source_hash = self.__target_hash = [x.value for x in list(self.__push_target_hash_query.find(self.event_context.message))][0] return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService) -> ScanInspector: if await self.__is_pr_draft(): - AzureDevOpsEnterpriseOrchestrator.log().info(f"Skipping draft PR {AzureDevOpsEnterpriseOrchestrator.__pr_self_link_query.find(self.__json)[0].value}") + AzureDevOpsEnterpriseOrchestrator.log().info(f"Skipping draft PR {AzureDevOpsEnterpriseOrchestrator.__pr_self_link_query.find(self.event_context.message)[0].value}") return - self.__source_branch = OrchestratorBase.normalize_branch_name([x.value for x in list(self.__pr_frombranch_query.find(self.__json))][0]) - self.__target_branch = OrchestratorBase.normalize_branch_name([x.value for x in list(self.__pr_tobranch_query.find(self.__json))][0]) - self.__source_hash = [x.value for x in list(self.__pr_fromhash_query.find(self.__json))][0] - self.__target_hash = [x.value for x in list(self.__pr_tohash_query.find(self.__json))][0] - self.__pr_id = str([x.value for x in list(self.__pr_id_query.find(self.__json))][0]) + self.__source_branch = OrchestratorBase.normalize_branch_name([x.value for x in list(self.__pr_frombranch_query.find(self.event_context.message))][0]) + self.__target_branch = OrchestratorBase.normalize_branch_name([x.value for x in list(self.__pr_tobranch_query.find(self.event_context.message))][0]) + self.__source_hash = [x.value for x in list(self.__pr_fromhash_query.find(self.event_context.message))][0] + self.__target_hash = [x.value for x in list(self.__pr_tohash_query.find(self.event_context.message))][0] + self.__pr_id = str([x.value for x in list(self.__pr_id_query.find(self.event_context.message))][0]) - statuses = list(set([AzureDevOpsEnterpriseOrchestrator.__pr_status_map[x.value] for x in AzureDevOpsEnterpriseOrchestrator.__pr_reviewer_status_query.find(self.__json)])) + statuses = list(set([AzureDevOpsEnterpriseOrchestrator.__pr_status_map[x.value] for x in AzureDevOpsEnterpriseOrchestrator.__pr_reviewer_status_query.find(self.event_context.message)])) if not len(statuses) > 0: self.__pr_status = "NO_REVIEWERS" else: self.__pr_status = "/".join(statuses) - self.__pr_state = AzureDevOpsEnterpriseOrchestrator.__pr_state_query.find(self.__json)[0].value + self.__pr_state = AzureDevOpsEnterpriseOrchestrator.__pr_state_query.find(self.event_context.message)[0].value existing_scans = await cxone_service.find_pr_scans(await self.get_cxone_project_name(), self.__pr_id, self.__source_hash) @@ -176,7 +175,7 @@ async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_serv @property def __repository_id(self) -> str: - return [x.value for x in list(self.__repository_id_query.find(self.__json))][0] + return [x.value for x in list(self.__repository_id_query.find(self.event_context.message))][0] @property diff --git a/orchestration/base.py b/orchestration/base.py index ae61e8d..ea50ff0 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -9,6 +9,8 @@ from scm_services.cloner import Cloner, CloneWorker from workflows.state_service import WorkflowStateService from workflows.messaging import PRDetails +from api_utils.auth_factories import EventContext +from typing import Dict class OrchestratorBase: @@ -20,25 +22,20 @@ def normalize_branch_name(branch): def log(clazz) -> logging.Logger: return logging.getLogger(clazz.__name__) - def __init__(self, headers, webhook_payload): - self.__webhook_payload = webhook_payload - self.__headers = headers + def __init__(self, event_context : EventContext): + self.__event_context = event_context @property def config_key(self): raise NotImplementedError("config_key") @property - def _headers(self) -> dict: - return self.__headers + def event_context(self) -> EventContext: + return self.__event_context @property def route_urls(self) -> list: raise NotImplementedError("route_urls") - - @property - def _webhook_payload(self) -> str: - return self.__webhook_payload @staticmethod def __get_path_dict(path : str, root : str = None) -> dict: @@ -57,7 +54,7 @@ def __get_path_dict(path : str, root : str = None) -> dict: def get_header_key_safe(self, key): try: - return self.__headers[key] + return self.event_context.headers[key] except: return None diff --git a/orchestration/bbdc.py b/orchestration/bbdc.py index 46cd7ee..783a352 100644 --- a/orchestration/bbdc.py +++ b/orchestration/bbdc.py @@ -1,5 +1,5 @@ from .base import OrchestratorBase -import json +from api_utils.auth_factories import EventContext from api_utils import signature from jsonpath_ng import parse from .exceptions import OrchestrationException @@ -38,8 +38,8 @@ class BitBucketDataCenterOrchestrator(OrchestratorBase): def config_key(self): return "bbdc" - def __init__(self, headers : dict, webhook_payload : dict): - OrchestratorBase.__init__(self, headers, webhook_payload) + def __init__(self, event_context : EventContext): + OrchestratorBase.__init__(self, event_context) self.__isdiagnostic = False @@ -49,10 +49,8 @@ def __init__(self, headers : dict, webhook_payload : dict): self.__isdiagnostic = True return - self.__json = json.loads(webhook_payload) - - self.__clone_urls = {x.value['name']:x.value['href'] for x in BitBucketDataCenterOrchestrator.__push_route_urls_query.find(self.__json) } | \ - {x.value['name']:x.value['href'] for x in BitBucketDataCenterOrchestrator.__pr_route_urls_query.find(self.__json) } + self.__clone_urls = {x.value['name']:x.value['href'] for x in BitBucketDataCenterOrchestrator.__push_route_urls_query.find(self.event_context.message) } | \ + {x.value['name']:x.value['href'] for x in BitBucketDataCenterOrchestrator.__pr_route_urls_query.find(self.event_context.message) } self.__route_urls = list(self.__clone_urls.values()) @@ -68,7 +66,7 @@ async def is_signature_valid(self, shared_secret : str) -> bool: return False hashalg,hash = sig.split("=") - payload_hash = signature.get(hashalg, shared_secret, self._webhook_payload) + payload_hash = signature.get(hashalg, shared_secret, self.event_context.raw_event_payload) return hash == payload_hash @@ -88,42 +86,42 @@ async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_se self.__source_branch = self.__target_branch = None self.__source_hash = self.__target_hash = None - if len([x.value for x in BitBucketDataCenterOrchestrator.__push_change_types_query.find(self.__json) \ + if len([x.value for x in BitBucketDataCenterOrchestrator.__push_change_types_query.find(self.event_context.message) \ if x.value in BitBucketDataCenterOrchestrator.__push_scannable_change_types]) > 0: - first_change = BitBucketDataCenterOrchestrator.__push_changes_extract_query.find(self.__json)[0].value + first_change = BitBucketDataCenterOrchestrator.__push_changes_extract_query.find(self.event_context.message)[0].value self.__source_branch = self.__target_branch = first_change['ref']['displayId'] self.__source_hash = self.__target_hash = first_change['toHash'] - self.__repo_project_key = BitBucketDataCenterOrchestrator.__push_repo_project_key_query.find(self.__json)[0].value - self.__repo_project_name = BitBucketDataCenterOrchestrator.__push_repo_project_name_query.find(self.__json)[0].value - self.__repo_slug = BitBucketDataCenterOrchestrator.__push_repo_slug_query.find(self.__json)[0].value - self.__repo_name = BitBucketDataCenterOrchestrator.__push_repo_name_query.find(self.__json)[0].value + self.__repo_project_key = BitBucketDataCenterOrchestrator.__push_repo_project_key_query.find(self.event_context.message)[0].value + self.__repo_project_name = BitBucketDataCenterOrchestrator.__push_repo_project_name_query.find(self.event_context.message)[0].value + self.__repo_slug = BitBucketDataCenterOrchestrator.__push_repo_slug_query.find(self.event_context.message)[0].value + self.__repo_name = BitBucketDataCenterOrchestrator.__push_repo_name_query.find(self.event_context.message)[0].value return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) async def __is_pr_draft(self) -> bool: - return bool(BitBucketDataCenterOrchestrator.__pr_draft_query.find(self.__json)[0].value) + return bool(BitBucketDataCenterOrchestrator.__pr_draft_query.find(self.event_context.message)[0].value) def __populate_common_pr_data(self): - toref = BitBucketDataCenterOrchestrator.__pr_toref_extract_query.find(self.__json)[0].value + toref = BitBucketDataCenterOrchestrator.__pr_toref_extract_query.find(self.event_context.message)[0].value self.__target_branch = toref['displayId'] self.__target_hash = toref['latestCommit'] - fromref = BitBucketDataCenterOrchestrator.__pr_fromref_extract_query.find(self.__json)[0].value + fromref = BitBucketDataCenterOrchestrator.__pr_fromref_extract_query.find(self.event_context.message)[0].value self.__source_branch = fromref['displayId'] self.__source_hash = fromref['latestCommit'] - self.__repo_project_key = BitBucketDataCenterOrchestrator.__pr_repo_project_key_query.find(self.__json)[0].value - self.__repo_project_name = BitBucketDataCenterOrchestrator.__pr_repo_project_name_query.find(self.__json)[0].value - self.__repo_slug = BitBucketDataCenterOrchestrator.__pr_repo_slug_query.find(self.__json)[0].value - self.__repo_name = BitBucketDataCenterOrchestrator.__pr_repo_name_query.find(self.__json)[0].value - self.__pr_id = str(BitBucketDataCenterOrchestrator.__pr_id_query.find(self.__json)[0].value) - self.__pr_state = BitBucketDataCenterOrchestrator.__pr_state_query.find(self.__json)[0].value + self.__repo_project_key = BitBucketDataCenterOrchestrator.__pr_repo_project_key_query.find(self.event_context.message)[0].value + self.__repo_project_name = BitBucketDataCenterOrchestrator.__pr_repo_project_name_query.find(self.event_context.message)[0].value + self.__repo_slug = BitBucketDataCenterOrchestrator.__pr_repo_slug_query.find(self.event_context.message)[0].value + self.__repo_name = BitBucketDataCenterOrchestrator.__pr_repo_name_query.find(self.event_context.message)[0].value + self.__pr_id = str(BitBucketDataCenterOrchestrator.__pr_id_query.find(self.event_context.message)[0].value) + self.__pr_state = BitBucketDataCenterOrchestrator.__pr_state_query.find(self.event_context.message)[0].value - statuses = list(set([x.value for x in BitBucketDataCenterOrchestrator.__pr_reviewer_status_query.find(self.__json)])) + statuses = list(set([x.value for x in BitBucketDataCenterOrchestrator.__pr_reviewer_status_query.find(self.event_context.message)])) if not len(statuses) > 0: self.__pr_status = "NO_REVIEWERS" @@ -132,14 +130,14 @@ def __populate_common_pr_data(self): async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService) -> ScanInspector: if await self.__is_pr_draft(): - BitBucketDataCenterOrchestrator.log().info(f"Skipping draft PR {BitBucketDataCenterOrchestrator.__pr_self_link_query.find(self.__json)[0].value}") + BitBucketDataCenterOrchestrator.log().info(f"Skipping draft PR {BitBucketDataCenterOrchestrator.__pr_self_link_query.find(self.event_context.message)[0].value}") return self.__populate_common_pr_data() return await OrchestratorBase._execute_pr_scan_workflow(self, cxone_service, scm_service, workflow_service) async def _execute_pr_tag_update_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): if await self.__is_pr_draft(): - BitBucketDataCenterOrchestrator.log().info(f"Skipping draft PR {BitBucketDataCenterOrchestrator.__pr_self_link_query.find(self.__json)[0].value}") + BitBucketDataCenterOrchestrator.log().info(f"Skipping draft PR {BitBucketDataCenterOrchestrator.__pr_self_link_query.find(self.event_context.message)[0].value}") return self.__populate_common_pr_data() diff --git a/orchestration/gh.py b/orchestration/gh.py index 1960af5..5a7b457 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -1,8 +1,8 @@ from .base import OrchestratorBase from api_utils import signature from api_utils.pagers import async_api_page_generator +from api_utils.auth_factories import EventContext from jsonpath_ng import parse -import json from cxone_service import CxOneService from cxone_api.util import json_on_ok from scm_services import SCMService @@ -76,9 +76,9 @@ def is_diagnostic(self) -> bool: return self.__isdiagnostic async def __log_app_install(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): - sender = GithubOrchestrator.__install_sender_query.find(self.__json)[0].value - target = GithubOrchestrator.__install_target_query.find(self.__json)[0].value - target_type = GithubOrchestrator.__install_target_type_query.find(self.__json)[0].value + sender = GithubOrchestrator.__install_sender_query.find(self.event_context.message)[0].value + target = GithubOrchestrator.__install_target_query.find(self.event_context.message)[0].value + target_type = GithubOrchestrator.__install_target_type_query.find(self.event_context.message)[0].value GithubOrchestrator.log().info(f"Install event '{self.__action}': Initiated by [{sender}] on {target_type} [{target}]") if self.__action in ["created", "new_permissions_accepted", "added"]: @@ -88,12 +88,12 @@ async def __log_app_install(self, cxone_service : CxOneService, scm_service : SC GithubOrchestrator.log().warning(f"Install target '{target}' is type '{target_type}' but expected to be 'Organization'.") warned = True - repo_selection = GithubOrchestrator.__install_repo_selection_query.find(self.__json)[0].value + repo_selection = GithubOrchestrator.__install_repo_selection_query.find(self.event_context.message)[0].value if not repo_selection == "all": GithubOrchestrator.log().warning(f"Repository selection is '{repo_selection}' but expected to be 'all'.") warned = True - events = GithubOrchestrator.__install_events_query.find(self.__json) + events = GithubOrchestrator.__install_events_query.find(self.event_context.message) if len(events) == 0: unhandled_events = GithubOrchestrator.__expected_events else: @@ -104,7 +104,7 @@ async def __log_app_install(self, cxone_service : CxOneService, scm_service : SC permissions_not_found = dict(GithubOrchestrator.__expected_permissions) - payload_permissions = GithubOrchestrator.__install_permissions_query.find(self.__json) + payload_permissions = GithubOrchestrator.__install_permissions_query.find(self.event_context.message) if len(payload_permissions) > 0: permissions = payload_permissions[0].value for p in permissions.keys(): @@ -126,31 +126,29 @@ async def __log_app_install(self, cxone_service : CxOneService, scm_service : SC GithubOrchestrator.log().info("The GitHub app appears to be properly configured.") def __installation_route_urls(self): - return [GithubOrchestrator.__install_route_url_query.find(self.__json)[0].value] + return [GithubOrchestrator.__install_route_url_query.find(self.event_context.message)[0].value] def __code_event_route_urls(self): - return [GithubOrchestrator.__code_event_route_url_query.find(self.__json)[0].value] + return [GithubOrchestrator.__code_event_route_url_query.find(self.event_context.message)[0].value] def __code_event_clone_urls(self): return { - "ssh" : GithubOrchestrator.__code_event_ssh_clone_url_query.find(self.__json)[0].value, - "http" : GithubOrchestrator.__code_event_http_clone_url_query.find(self.__json)[0].value + "ssh" : GithubOrchestrator.__code_event_ssh_clone_url_query.find(self.event_context.message)[0].value, + "http" : GithubOrchestrator.__code_event_http_clone_url_query.find(self.event_context.message)[0].value } - def __init__(self, headers : dict, webhook_payload : dict): - OrchestratorBase.__init__(self, headers, webhook_payload) + def __init__(self, event_context : EventContext): + OrchestratorBase.__init__(self, event_context) self.__isdiagnostic = False - self.__event = self.get_header_key_safe('X-GitHub-Event') + self.__event = self.get_header_key_safe('X-Github-Event') if not self.__event is None and self.__event == "ping": self.__isdiagnostic = True return - self.__json = json.loads(webhook_payload) - - action_found = GithubOrchestrator.__event_action_query.find(self.__json) + action_found = GithubOrchestrator.__event_action_query.find(self.event_context.message) if len(action_found) > 0: self.__action = action_found[0].value self.__dispatch_event = f"{self.__event}:{self.__action if self.__action is not None else ""}" @@ -172,7 +170,7 @@ async def execute(self, cxone_service : CxOneService, scm_service : SCMService, return await GithubOrchestrator.__workflow_map[self.__dispatch_event](self, cxone_service, scm_service, workflow_service) async def _get_clone_worker(self, scm_service : SCMService, clone_url : str) -> CloneWorker: - return scm_service.cloner.clone(clone_url, self.__json) + return scm_service.cloner.clone(clone_url, self.event_context) async def _get_target_branch_and_hash(self) -> tuple: return self.__target_branch, self.__target_hash @@ -192,26 +190,26 @@ async def is_signature_valid(self, shared_secret : str) -> bool: return False hashalg,hash = sig.split("=") - payload_hash = signature.get(hashalg, shared_secret, self._webhook_payload) + payload_hash = signature.get(hashalg, shared_secret, self.event_context.raw_event_payload) return hash == payload_hash async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): self.__target_branch = self.__source_branch = OrchestratorBase.normalize_branch_name( - GithubOrchestrator.__push_target_branch_query.find(self.__json)[0].value) - self.__target_hash = self.__source_hash = GithubOrchestrator.__push_target_hash_query.find(self.__json)[0].value + GithubOrchestrator.__push_target_branch_query.find(self.event_context.message)[0].value) + self.__target_hash = self.__source_hash = GithubOrchestrator.__push_target_hash_query.find(self.event_context.message)[0].value - self.__project_key = GithubOrchestrator.__push_project_key_query.find(self.__json)[0].value - self.__org = GithubOrchestrator.__push_org_key_query.find(self.__json)[0].value + self.__project_key = GithubOrchestrator.__push_project_key_query.find(self.event_context.message)[0].value + self.__org = GithubOrchestrator.__push_org_key_query.find(self.event_context.message)[0].value return await OrchestratorBase._execute_push_scan_workflow(self, cxone_service, scm_service, workflow_service) def __get_pr_assignees(self): ret = [] - assignee = GithubOrchestrator.__pull_assignee_query.find(self.__json) - assignees = GithubOrchestrator.__pull_assignees_query.find(self.__json) + assignee = GithubOrchestrator.__pull_assignee_query.find(self.event_context.message) + assignees = GithubOrchestrator.__pull_assignees_query.find(self.event_context.message) if len(assignees) > 0 and assignees[0].value is not None and len(assignees[0].value) > 0: ret = assignees[0].value @@ -222,23 +220,23 @@ def __get_pr_assignees(self): return ret def __get_pr_reviewers(self): - reviewers = GithubOrchestrator.__pull_requested_reviewers_query.find(self.__json) + reviewers = GithubOrchestrator.__pull_requested_reviewers_query.find(self.event_context.message) if len(reviewers) > 0 and reviewers[0].value is not None: return reviewers[0].value return [] def __populate_common_pr_data(self): - self.__pr_html_url = GithubOrchestrator.__pull_html_url.find(self.__json)[0].value - self.__pr_id = GithubOrchestrator.__pull_id_query.find(self.__json)[0].value - self.__project_key = GithubOrchestrator.__pull_project_key_query.find(self.__json)[0].value - self.__org = GithubOrchestrator.__pull_org_key_query.find(self.__json)[0].value - self.__target_branch = GithubOrchestrator.__pull_target_branch_query.find(self.__json)[0].value - self.__source_branch = GithubOrchestrator.__pull_source_branch_query.find(self.__json)[0].value - self.__target_hash = GithubOrchestrator.__pull_target_hash_query.find(self.__json)[0].value - self.__source_hash = GithubOrchestrator.__pull_source_hash_query.find(self.__json)[0].value - self.__is_draft = bool(GithubOrchestrator.__pull_draft_query.find(self.__json)[0].value) - self.__pr_state = f"{GithubOrchestrator.__pull_state_query.find(self.__json)[0].value}{"-draft" if self.__is_draft else ""}" + self.__pr_html_url = GithubOrchestrator.__pull_html_url.find(self.event_context.message)[0].value + self.__pr_id = GithubOrchestrator.__pull_id_query.find(self.event_context.message)[0].value + self.__project_key = GithubOrchestrator.__pull_project_key_query.find(self.event_context.message)[0].value + self.__org = GithubOrchestrator.__pull_org_key_query.find(self.event_context.message)[0].value + self.__target_branch = GithubOrchestrator.__pull_target_branch_query.find(self.event_context.message)[0].value + self.__source_branch = GithubOrchestrator.__pull_source_branch_query.find(self.event_context.message)[0].value + self.__target_hash = GithubOrchestrator.__pull_target_hash_query.find(self.event_context.message)[0].value + self.__source_hash = GithubOrchestrator.__pull_source_hash_query.find(self.event_context.message)[0].value + self.__is_draft = bool(GithubOrchestrator.__pull_draft_query.find(self.event_context.message)[0].value) + self.__pr_state = f"{GithubOrchestrator.__pull_state_query.find(self.event_context.message)[0].value}{"-draft" if self.__is_draft else ""}" if len(self.__get_pr_assignees()) > 0: self.__pr_status = "REVIEWERS_ASSIGNED" @@ -289,19 +287,17 @@ def args_gen(offset : int): "method" : "GET", "path" : f"/repos/{self._repo_organization}/{self._repo_project_key}/branches", "query" : { "protected" : True, "per_page" : GithubOrchestrator.__api_page_max, "page" : offset + 1}, - "event_msg" : self.__json + "event_context" : self.event_context } async for branch in async_api_page_generator(scm_service.exec, data_extractor, args_gen): ret_branches.append(branch) if len(ret_branches) == 0: - ret_branches.append(GithubOrchestrator.__code_event_default_branch_name_extract.find(self.__json)[0].value) + ret_branches.append(GithubOrchestrator.__code_event_default_branch_name_extract.find(self.event_context.message)[0].value) return ret_branches - - @property def _repo_project_key(self) -> str: return self.__project_key diff --git a/scm_services/cloner.py b/scm_services/cloner.py index d427862..bc31160 100644 --- a/scm_services/cloner.py +++ b/scm_services/cloner.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import Dict, List from api_utils.auth_factories import GithubAppAuthFactory +from api_utils.auth_factories import EventContext class CloneWorker: def __init__(self, clone_thread, clone_dest_path): @@ -115,7 +116,7 @@ def select_protocol_from_supported(self, protocol_list): return x return None - async def _fix_clone_url(self, clone_url : str, event_context : Dict=None, force_reauth : bool=False): + async def _fix_clone_url(self, clone_url : str, event_context : EventContext=None, force_reauth : bool=False): return clone_url @property @@ -129,7 +130,7 @@ def destination_port(self): async def _get_clone_cmd_stub(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> List: return self.__clone_cmd_stub - async def clone(self, clone_url, event_context : Dict=None, force_reauth : bool=False): + async def clone(self, clone_url, event_context : EventContext=None, force_reauth : bool=False): Cloner.log().debug(f"Clone Execution for: {clone_url}") fixed_clone_url = await self._fix_clone_url(clone_url, event_context, force_reauth) @@ -157,7 +158,7 @@ def __init__(self, username : str, password : str): self.__username = username self.__password = password - async def _fix_clone_url(self, clone_url : str, event_context : Dict=None, force_reauth : bool=False): + async def _fix_clone_url(self, clone_url : str, event_context : EventContext=None, force_reauth : bool=False): return Cloner.insert_creds_in_url(clone_url, self.__username, self.__password) @@ -166,6 +167,6 @@ def __init__(self, auth_factory : GithubAppAuthFactory): Cloner.__init__(self) self.__auth_factory = auth_factory - async def _fix_clone_url(self, clone_url : str, event_context : Dict=None, force_reauth : bool=False): + async def _fix_clone_url(self, clone_url : str, event_context : EventContext=None, force_reauth : bool=False): token = SecretRegistry.register(await self.__auth_factory.get_token(event_context, force_reauth)) return Cloner.insert_creds_in_url(clone_url, "x-access-token", token) diff --git a/scm_services/scm.py b/scm_services/scm.py index 8818a62..c0257cd 100644 --- a/scm_services/scm.py +++ b/scm_services/scm.py @@ -3,6 +3,7 @@ from scm_services.cloner import Cloner from typing import Dict, Any from requests import Response +from api_utils.auth_factories import EventContext class SCMService: @@ -33,8 +34,8 @@ def shared_secret(self): def _form_url(self, url_path, anchor=None, **kwargs): return self.__session._form_url(url_path, anchor, **kwargs) - async def exec(self, method : str, path : str, query : Dict=None, body : Any=None, extra_headers : Dict=None, event_msg : Dict=None) -> Response: - return await self.__session.exec(event_msg, method, path, query, body, extra_headers) + async def exec(self, method : str, path : str, query : Dict=None, body : Any=None, extra_headers : Dict=None, event_context : EventContext=None) -> Response: + return await self.__session.exec(event_context, method, path, query, body, extra_headers) async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, summary_markdown : str, event_msg : Dict=None): diff --git a/wsgi.py b/wsgi.py index a7fc749..08a0624 100644 --- a/wsgi.py +++ b/wsgi.py @@ -13,6 +13,7 @@ from time import perf_counter_ns from task_management import TaskManager import cxoneflow_logging as cof_logging +from api_utils.auth_factories import EventContext, HeaderFilteredEventContext cof_logging.bootstrap() @@ -44,7 +45,7 @@ async def bbdc_webhook_endpoint(): __log.info("Received hook for BitBucket Data Center") __log.debug(f"bbdc webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - TaskManager.in_background(OrchestrationDispatch.execute(BitBucketDataCenterOrchestrator(request.headers, request.data))) + TaskManager.in_background(OrchestrationDispatch.execute(BitBucketDataCenterOrchestrator(EventContext(request.data, request.headers)))) return Response(status=204) except Exception as ex: __log.exception(ex) @@ -55,7 +56,7 @@ async def github_webhook_endpoint(): __log.info("Received hook for Github") __log.debug(f"github webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - orch = GithubOrchestrator(request.headers, request.data) + orch = GithubOrchestrator(HeaderFilteredEventContext(request.data, request.headers, "User-Agent|X-(Git)?[H|h]ub")) if not orch.is_diagnostic: TaskManager.in_background(OrchestrationDispatch.execute(orch)) @@ -76,7 +77,7 @@ async def adoe_webhook_endpoint(): __log.info("Received hook for Azure DevOps Enterprise") __log.debug(f"adoe webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - orch = AzureDevOpsEnterpriseOrchestrator(request.headers, request.data); + orch = AzureDevOpsEnterpriseOrchestrator(EventContext(request.data, request.headers)); if not orch.is_diagnostic: TaskManager.in_background(OrchestrationDispatch.execute(orch)) From e1e23b72fd5ac1092776fc815f347f85ec9bddbc Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Wed, 25 Sep 2024 21:59:20 +0000 Subject: [PATCH 20/35] ghe pr feedback basically working --- api_utils/__init__.py | 7 ++++++- api_utils/apisession.py | 14 +++----------- api_utils/auth_factories.py | 29 +++++++++++----------------- config/__init__.py | 4 +++- orchestration/base.py | 2 +- orchestration/gh.py | 2 +- scm_services/adoe.py | 7 ++++--- scm_services/bbdc.py | 7 ++++--- scm_services/gh.py | 17 ++++++++++------ scm_services/scm.py | 10 +++++++--- workflows/messaging/v1/pr_details.py | 2 ++ workflows/state_service.py | 4 ++-- 12 files changed, 55 insertions(+), 50 deletions(-) diff --git a/api_utils/__init__.py b/api_utils/__init__.py index 3544728..602621f 100644 --- a/api_utils/__init__.py +++ b/api_utils/__init__.py @@ -2,7 +2,7 @@ from .auth_factories import AuthFactory, StaticAuthFactory from requests.auth import HTTPBasicAuth from .bearer import HTTPBearerAuth - +import urllib def auth_basic(username, password) -> AuthFactory: @@ -23,4 +23,9 @@ def verify_signature(signature_header, secret, body) -> bool: return generated_hash == hash +def form_url(endpoint : str, url_path : str, anchor=None, **kwargs): + base = endpoint.rstrip("/") + suffix = urllib.parse.quote(url_path.lstrip("/")) + args = [f"{x}={urllib.parse.quote(str(kwargs[x]))}" for x in kwargs.keys()] + return f"{base}/{suffix}{"?" if len(args) > 0 else ""}{"&".join(args)}{f"#{anchor}" if anchor is not None else ""}" diff --git a/api_utils/apisession.py b/api_utils/apisession.py index 4faeb17..47aaf66 100644 --- a/api_utils/apisession.py +++ b/api_utils/apisession.py @@ -2,9 +2,10 @@ from requests import Response from requests import request from typing import Dict, Union, Any -import urllib, logging, sys, asyncio +import logging, sys, asyncio from api_utils import AuthFactory from api_utils.auth_factories import EventContext +from . import form_url class SCMAuthException(Exception): pass @@ -37,21 +38,12 @@ def form_api_endpoint(base_endpoint : str, suffix : str): ret = f"{ret}/{suffix.lstrip("/").rstrip("/")}" return ret - @property def api_endpoint(self): return self.__api_endpoint - - def _form_url(self, url_path, anchor=None, **kwargs): - base = self.api_endpoint - suffix = urllib.parse.quote(url_path.lstrip("/")) - args = [f"{x}={urllib.parse.quote(str(kwargs[x]))}" for x in kwargs.keys()] - return f"{base}/{suffix}{"?" if len(args) > 0 else ""}{"&".join(args)}{f"#{anchor}" if anchor is not None else ""}" - - async def exec(self, event_context : EventContext, method : str, path : str, query : Dict = None, body : Any = None, extra_headers : Dict = None) -> Response: - url = self._form_url(path) + url = form_url(self.api_endpoint, path) headers = dict(self.__headers) if not extra_headers is None: headers.update(extra_headers) diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index 07a4fc1..53f359f 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -9,34 +9,27 @@ from api_utils.bearer import HTTPBearerAuth from cxone_api.util import json_on_ok from cxoneflow_logging import SecretRegistry +from dataclasses import dataclass, field +from dataclasses_json import dataclass_json class AuthFactoryException(BaseException): pass +@dataclass_json +@dataclass class EventContext: - def __init__(self, event_payload : Dict, event_headers : Dict): - self.__payload = event_payload - self.__msg = json.loads(event_payload) - self.__headers = event_headers - - @property - def message(self) -> Dict: - return self.__msg - - @property - def raw_event_payload(self): - return self.__payload - + raw_event_payload : bytes = field(repr=False) + headers : Dict + message : Dict = field(init=False) - @property - def headers(self) -> Dict: - return self.__headers + def __post_init__(self): + self.message = json.loads(self.raw_event_payload) class HeaderFilteredEventContext(EventContext): - def __init__(self, event_payload : Dict, event_headers : Dict, header_key_regex : str): + def __init__(self, raw_event_payload : str, headers : Dict, header_key_regex : str): pattern = re.compile(header_key_regex) - EventContext.__init__(self, event_payload, {k:v for k,v in event_headers if pattern.match(k)}) + EventContext.__init__(self, raw_event_payload=raw_event_payload, headers={k:v for k,v in headers if pattern.match(k)}) diff --git a/config/__init__.py b/config/__init__.py index 301248a..ba7d12e 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -396,6 +396,8 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config api_auth_dict = CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'api-auth', connection_config_dict) + display_url = CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict) + api_url = APISession.form_api_endpoint(CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict), CxOneFlowConfig.__get_value_for_key_or_default('api-url-suffix', connection_config_dict, None)) @@ -418,7 +420,7 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config clone_auth_dict = api_auth_dict clone_config_path = f"{config_path}/connection/api-auth" - scm_service = scm_class(service_moniker, api_session, scm_shared_secret, + scm_service = scm_class(display_url, service_moniker, api_session, scm_shared_secret, CxOneFlowConfig.__cloner_factory(api_session, cloner_factory, clone_auth_dict, clone_config_path)) return repo_matcher, cxone_service, scm_service, workflow_service_client diff --git a/orchestration/base.py b/orchestration/base.py index ea50ff0..4ac861c 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -151,7 +151,7 @@ async def _execute_pr_scan_workflow(self, cxone_service : CxOneService, scm_serv inspector = await self.__exec_scan(cxone_service, scm_service, scan_tags) if inspector is not None: await workflow_service.start_pr_scan_workflow(inspector.project_id, inspector.scan_id, - PRDetails(clone_url=self._repo_clone_url(scm_service.cloner), + PRDetails(event_context=self.event_context, clone_url=self._repo_clone_url(scm_service.cloner), repo_project=self._repo_project_key, repo_slug=self._repo_slug, organization=self._repo_organization, pr_id=self._pr_id, source_branch=source_branch, target_branch=target_branch)) diff --git a/orchestration/gh.py b/orchestration/gh.py index 5a7b457..7f891c1 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -37,7 +37,7 @@ class GithubOrchestrator(OrchestratorBase): __pull_target_hash_query = parse("$.pull_request.base.sha") __pull_source_branch_query = parse("$.pull_request.head.ref") __pull_source_hash_query = parse("$.pull_request.head.sha") - __pull_id_query = parse("$.pull_request.id") + __pull_id_query = parse("$.number") __pull_state_query = parse("$.pull_request.state") __pull_draft_query = parse("$.pull_request.draft") __pull_html_url = parse("$.pull_request.html_url") diff --git a/scm_services/adoe.py b/scm_services/adoe.py index 694cfa0..b244e1e 100644 --- a/scm_services/adoe.py +++ b/scm_services/adoe.py @@ -5,7 +5,8 @@ from datetime import datetime, UTC import markdown as md from typing import Dict - +from api_utils.auth_factories import EventContext +from api_utils import form_url class ADOEService(SCMService): @@ -76,7 +77,7 @@ async def __create_pr_thread(self, organization : str, project : str, repo_slug async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, - scanid : str, full_markdown : str, summary_markdown : str, event_msg : Dict=None): + scanid : str, full_markdown : str, summary_markdown : str, event_context : EventContext): existing_thread = await self.__get_pr_thread(organization, project, repo_slug, pr_number) content = md.markdown(full_markdown, extensions=['tables']) @@ -91,5 +92,5 @@ async def exec_pr_decorate(self, organization : str, project : str, repo_slug : def create_code_permalink(self, organization : str, project : str, repo_slug : str, branch : str, code_path : str, code_line : str): - return self._form_url(f"{organization}/{project}/_git/{repo_slug}", path=code_path, version=f"GB{branch}", + return form_url(self.display_url, f"{organization}/{project}/_git/{repo_slug}", path=code_path, version=f"GB{branch}", line=code_line, lineEnd=code_line, lineStartColumn=0, lineEndColumn=1024, lineStyle="plain", _a="contents") diff --git a/scm_services/bbdc.py b/scm_services/bbdc.py index 3527a6b..a4a3483 100644 --- a/scm_services/bbdc.py +++ b/scm_services/bbdc.py @@ -2,7 +2,8 @@ from cxone_api.util import json_on_ok import json from workflows.pr import PullRequestDecoration -from typing import Dict +from api_utils.auth_factories import EventContext +from api_utils import form_url class BBDCService(SCMService): __max_content_chars = 32000 @@ -55,7 +56,7 @@ async def __find_existing_comment(self, project : str, repo_slug : str, pr_numbe return None, None async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, - summary_markdown : str, event_msg : Dict=None): + summary_markdown : str, event_context : EventContext): id, version = await self.__find_existing_comment(project, repo_slug, pr_number) content = full_markdown if len(full_markdown) <= BBDCService.__max_content_chars else summary_markdown @@ -68,5 +69,5 @@ async def exec_pr_decorate(self, organization : str, project : str, repo_slug : SCMService.log().debug(f"Comment {id} version {version} modified on PR {pr_number}") def create_code_permalink(self, organization : str, project : str, repo_slug : str, branch : str, code_path : str, code_line : str): - return self._form_url(f"projects/{project}/repos/{repo_slug}/browse{code_path}", anchor=code_line, at=branch) + return form_url(self.display_url, f"projects/{project}/repos/{repo_slug}/browse{code_path}", anchor=code_line, at=branch) \ No newline at end of file diff --git a/scm_services/gh.py b/scm_services/gh.py index f5e12e9..5627050 100644 --- a/scm_services/gh.py +++ b/scm_services/gh.py @@ -1,14 +1,19 @@ from .scm import SCMService -from scm_services.cloner import Cloner -from typing import Dict - +from api_utils.auth_factories import EventContext +import json +from api_utils import form_url class GHService(SCMService): __max_content_chars = 65535 async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, - summary_markdown : str, event_msg : Dict=None): - raise NotImplementedError("exec_pr_decorate") + summary_markdown : str, event_context : EventContext): + + content = { "body" : full_markdown if len(full_markdown) <= GHService.__max_content_chars else summary_markdown} + + GHService.log().debug(await self.exec("POST", f"/repos/{organization}/{repo_slug}/issues/{pr_number}/comments", + body=json.dumps(content), event_context = event_context)) def create_code_permalink(self, organization : str, project : str, repo_slug : str, branch : str, code_path : str, code_line : str): - raise NotImplementedError("create_code_permalink") + return form_url(self.display_url, f"/{organization}/{repo_slug}/blob/{branch}{code_path}", f"L{code_line}") + diff --git a/scm_services/scm.py b/scm_services/scm.py index c0257cd..de13ecc 100644 --- a/scm_services/scm.py +++ b/scm_services/scm.py @@ -12,13 +12,17 @@ class SCMService: def log(clazz): return logging.getLogger(clazz.__name__) - def __init__(self, moniker : str, api_session : APISession, shared_secret : str, cloner : Cloner): + def __init__(self, display_url : str, moniker : str, api_session : APISession, shared_secret : str, cloner : Cloner): self.__session = api_session self.__shared_secret = shared_secret self.__cloner = cloner self.__moniker = moniker + self.__display_url = display_url - + @property + def display_url(self): + return self.__display_url + @property def moniker(self): return self.__moniker @@ -38,7 +42,7 @@ async def exec(self, method : str, path : str, query : Dict=None, body : Any=Non return await self.__session.exec(event_context, method, path, query, body, extra_headers) async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, - summary_markdown : str, event_msg : Dict=None): + summary_markdown : str, event_context : EventContext): raise NotImplementedError("exec_pr_decorate") def create_code_permalink(self, organization : str, project : str, repo_slug : str, branch : str, code_path : str, code_line : str): diff --git a/workflows/messaging/v1/pr_details.py b/workflows/messaging/v1/pr_details.py index d9efad5..2c491a4 100644 --- a/workflows/messaging/v1/pr_details.py +++ b/workflows/messaging/v1/pr_details.py @@ -1,5 +1,6 @@ from ..base_message import BaseMessage from dataclasses import dataclass +from api_utils.auth_factories import EventContext @dataclass(frozen=True) class PRDetails(BaseMessage): @@ -10,4 +11,5 @@ class PRDetails(BaseMessage): organization : str source_branch : str target_branch : str + event_context : EventContext schema : str = "v1" diff --git a/workflows/state_service.py b/workflows/state_service.py index c2a9c49..55da696 100644 --- a/workflows/state_service.py +++ b/workflows/state_service.py @@ -157,7 +157,7 @@ async def execute_pr_annotate_workflow(self, msg : aio_pika.abc.AbstractIncoming annotation = PullRequestAnnotation(cxone_service.display_link, inspector.project_id, am.scanid, am.annotation, pr_details.source_branch, self.__server_base_url) await scm_service.exec_pr_decorate(pr_details.organization, pr_details.repo_project, pr_details.repo_slug, pr_details.pr_id, - am.scanid, annotation.full_content, annotation.summary_content) + am.scanid, annotation.full_content, annotation.summary_content, pr_details.event_context) await msg.ack() else: WorkflowStateService.log().error(f"Unable for load scan {am.scanid}") @@ -183,7 +183,7 @@ async def execute_pr_feedback_workflow(self, msg : aio_pika.abc.AbstractIncoming self.__workflow_map[ScanWorkflow.PR].excluded_states, cxone_service.display_link, am.projectid, am.scanid, report, scm_service.create_code_permalink, pr_details, self.__server_base_url) await scm_service.exec_pr_decorate(pr_details.organization, pr_details.repo_project, pr_details.repo_slug, pr_details.pr_id, - am.scanid, feedback.full_content, feedback.summary_content) + am.scanid, feedback.full_content, feedback.summary_content, pr_details.event_context) await msg.ack() else: await msg.ack() From 0cc8a50f139211904951866c84de3c9b470c001f Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Wed, 25 Sep 2024 22:54:44 +0000 Subject: [PATCH 21/35] ghe pr updates complete --- orchestration/gh.py | 1 - scm_services/gh.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/orchestration/gh.py b/orchestration/gh.py index 7f891c1..8a403ab 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -4,7 +4,6 @@ from api_utils.auth_factories import EventContext from jsonpath_ng import parse from cxone_service import CxOneService -from cxone_api.util import json_on_ok from scm_services import SCMService from scm_services.cloner import CloneWorker from workflows.state_service import WorkflowStateService diff --git a/scm_services/gh.py b/scm_services/gh.py index 5627050..aaf1e83 100644 --- a/scm_services/gh.py +++ b/scm_services/gh.py @@ -1,18 +1,58 @@ from .scm import SCMService from api_utils.auth_factories import EventContext -import json +from api_utils.pagers import async_api_page_generator from api_utils import form_url +from requests import Response +from workflows.pr import PullRequestDecoration +from cxone_api.util import json_on_ok +import json class GHService(SCMService): __max_content_chars = 65535 + __api_page_max = 100 + + + def __comment_data_extractor(self, resp : Response): + if resp.ok: + json = resp.json() + return json, len(json) >= GHService.__api_page_max + return None + + def __comment_list_args_gen(self, path : str, event_context : EventContext, offset : int): + return { + "method" : "GET", + "path" : path, + "query" : {"per_page" : GHService.__api_page_max, "page" : offset + 1}, + "event_context" : event_context + } async def exec_pr_decorate(self, organization : str, project : str, repo_slug : str, pr_number : str, scanid : str, full_markdown : str, summary_markdown : str, event_context : EventContext): content = { "body" : full_markdown if len(full_markdown) <= GHService.__max_content_chars else summary_markdown} - GHService.log().debug(await self.exec("POST", f"/repos/{organization}/{repo_slug}/issues/{pr_number}/comments", + target_id = None + + async for comment in async_api_page_generator(self.exec, self.__comment_data_extractor, + lambda offset: self.__comment_list_args_gen(f"/repos/{organization}/{repo_slug}/issues/{pr_number}/comments", event_context, offset)): + if 'id' in comment.keys() and 'body' in comment.keys(): + comment_id = comment['id'] + if PullRequestDecoration.matches_identifier(comment['body']): + target_id = comment_id + break + + if target_id is None: + resp = json_on_ok(await self.exec("POST", f"/repos/{organization}/{repo_slug}/issues/{pr_number}/comments", body=json.dumps(content), event_context = event_context)) + action = "Created" + target_id = resp['id'] + else: + resp = json_on_ok(await self.exec("PATCH", f"/repos/{organization}/{repo_slug}/issues/comments/{target_id}", + body=json.dumps(content), event_context = event_context)) + action = "Updated" + + GHService.log().debug(f"{action} comment {target_id} in PR {pr_number}") + def create_code_permalink(self, organization : str, project : str, repo_slug : str, branch : str, code_path : str, code_line : str): return form_url(self.display_url, f"/{organization}/{repo_slug}/blob/{branch}{code_path}", f"L{code_line}") From 3a4a6fa254c671e01dd9bbd8a56b815b5c448fd5 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Thu, 26 Sep 2024 17:35:56 +0000 Subject: [PATCH 22/35] clone auth failure retries --- orchestration/base.py | 78 +++++++++++++++++++++++++----------------- orchestration/gh.py | 4 +-- scm_services/cloner.py | 17 +++++++-- test.py | 4 +-- 4 files changed, 64 insertions(+), 39 deletions(-) diff --git a/orchestration/base.py b/orchestration/base.py index 4ac861c..2c98940 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -6,7 +6,7 @@ from cxone_service import CxOneService from cxone_api.scanning import ScanInspector from scm_services import SCMService -from scm_services.cloner import Cloner, CloneWorker +from scm_services.cloner import Cloner, CloneWorker, CloneAuthException from workflows.state_service import WorkflowStateService from workflows.messaging import PRDetails from api_utils.auth_factories import EventContext @@ -61,7 +61,7 @@ def get_header_key_safe(self, key): async def execute(self, cxone_service: CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): raise NotImplementedError("execute") - async def _get_clone_worker(self, scm_service : SCMService, clone_url : str) -> CloneWorker: + async def _get_clone_worker(self, scm_service : SCMService, clone_url : str, failures : int) -> CloneWorker: return await scm_service.cloner.clone(clone_url) async def __exec_scan(self, cxone_service : CxOneService, scm_service : SCMService, tags) -> ScanInspector: @@ -80,40 +80,54 @@ async def __exec_scan(self, cxone_service : CxOneService, scm_service : SCMServi check = perf_counter_ns() OrchestratorBase.log().debug("Starting clone...") - async with await (await self._get_clone_worker(scm_service, clone_url)) as clone_worker: - code_path = await clone_worker.loc() + # Do 1 clone retry if there is an auth failure. + clone_auth_fails = 0 + while clone_auth_fails <= 1: + try: + async with await (await self._get_clone_worker(scm_service, clone_url, clone_auth_fails)) as clone_worker: + code_path = await clone_worker.loc() + + await scm_service.cloner.reset_head(code_path, source_hash) + + OrchestratorBase.log().info(f"{clone_url} cloned in {perf_counter_ns() - check}ns") + check = perf_counter_ns() + + with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: + with zipfile.ZipFile(zip_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as upload_payload: + zip_entries = OrchestratorBase.__get_path_dict(code_path) + + OrchestratorBase.log().debug(f"[{clone_url}][{source_branch}][{source_hash}] zipping for scan: {zip_entries}") + + for entry_key in zip_entries.keys(): + upload_payload.write(entry_key, zip_entries[entry_key]) + + OrchestratorBase.log().info(f"{clone_url} zipped in {perf_counter_ns() - check}ns") + + + try: + scan_submit = await cxone_service.execute_scan(zip_file.name, cxone_project_name, \ + source_branch, clone_url, tags) + + OrchestratorBase.log().debug(scan_submit) + OrchestratorBase.log().info(f"Scan id {scan_submit['id']} created for {clone_url}|{source_branch}|{source_hash}") + + return ScanInspector(scan_submit) + except Exception as ex: + OrchestratorBase.log().error(f"{clone_url}:{source_branch}@{source_hash}: No scan created due to exception: {ex}") + OrchestratorBase.log().exception(ex) + break + except CloneAuthException as cax: + if clone_auth_fails <= 1: + clone_auth_fails += 1 + OrchestratorBase.log().exception(cax) + else: + raise - await scm_service.cloner.reset_head(code_path, source_hash) - - OrchestratorBase.log().info(f"{clone_url} cloned in {perf_counter_ns() - check}ns") - check = perf_counter_ns() - - with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: - with zipfile.ZipFile(zip_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as upload_payload: - zip_entries = OrchestratorBase.__get_path_dict(code_path) - - OrchestratorBase.log().debug(f"[{clone_url}][{source_branch}][{source_hash}] zipping for scan: {zip_entries}") - - for entry_key in zip_entries.keys(): - upload_payload.write(entry_key, zip_entries[entry_key]) - - OrchestratorBase.log().info(f"{clone_url} zipped in {perf_counter_ns() - check}ns") - - - try: - scan_submit = await cxone_service.execute_scan(zip_file.name, cxone_project_name, \ - source_branch, clone_url, tags) - - OrchestratorBase.log().debug(scan_submit) - OrchestratorBase.log().info(f"Scan id {scan_submit['id']} created for {clone_url}|{source_branch}|{source_hash}") - - return ScanInspector(scan_submit) - except Exception as ex: - OrchestratorBase.log().error(f"{clone_url}:{source_branch}@{source_hash}: No scan created due to exception: {ex}") - OrchestratorBase.log().exception(ex) else: OrchestratorBase.log().info(f"{clone_url}:{source_hash}:{source_branch} is not related to any protected branch: {protected_branches}") + return + OrchestratorBase.log().warning("Scan not executed.") async def _execute_push_scan_workflow(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): OrchestratorBase.log().debug("_execute_push_scan_workflow") diff --git a/orchestration/gh.py b/orchestration/gh.py index 8a403ab..faa63a4 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -168,8 +168,8 @@ async def execute(self, cxone_service : CxOneService, scm_service : SCMService, else: return await GithubOrchestrator.__workflow_map[self.__dispatch_event](self, cxone_service, scm_service, workflow_service) - async def _get_clone_worker(self, scm_service : SCMService, clone_url : str) -> CloneWorker: - return scm_service.cloner.clone(clone_url, self.event_context) + async def _get_clone_worker(self, scm_service : SCMService, clone_url : str, failures : int) -> CloneWorker: + return scm_service.cloner.clone(clone_url, self.event_context, failures > 0) async def _get_target_branch_and_hash(self) -> tuple: return self.__target_branch, self.__target_hash diff --git a/scm_services/cloner.py b/scm_services/cloner.py index bc31160..2876075 100644 --- a/scm_services/cloner.py +++ b/scm_services/cloner.py @@ -5,21 +5,32 @@ from api_utils.auth_factories import GithubAppAuthFactory from api_utils.auth_factories import EventContext + +class CloneAuthException(BaseException): + pass + class CloneWorker: + + __stderr_auth_fail = re.compile(".*Invalid username or password.*") + __auth_fail_exit_code = 128 + def __init__(self, clone_thread, clone_dest_path): self.__log = logging.getLogger(f"CloneWorker:{clone_dest_path}") self.__clone_out_tempdir = clone_dest_path self.__clone_thread = clone_thread - async def loc(self) -> str: try: completed = await self.__clone_thread self.__log.debug(f"Clone task: return code [{completed.returncode}] stdout: [{completed.stdout}] stderr: [{completed.stderr}]") return self.__clone_out_tempdir.name except subprocess.CalledProcessError as ex: - self.__log.error(f"{ex} stdout: [{ex.stdout.decode('UTF-8')}] stderr: [{ex.stderr.decode('UTF-8')})]") - raise + if CloneWorker.__stderr_auth_fail.match(ex.stderr.decode('UTF-8').replace("\n", "")) and \ + ex.returncode == CloneWorker.__auth_fail_exit_code: + raise CloneAuthException(ex.stderr.decode('UTF-8')) + else: + self.__log.error(f"{ex} stdout: [{ex.stdout.decode('UTF-8')}] stderr: [{ex.stderr.decode('UTF-8')})]") + raise async def __aenter__(self): return self diff --git a/test.py b/test.py index e7ea4ff..617676c 100644 --- a/test.py +++ b/test.py @@ -5,7 +5,7 @@ pem = "./secrets/ghe-app-priv-key" -app_id = 5 +app_id = 6 with open(pem, "rb") as f: @@ -22,7 +22,7 @@ print(f"JWT: {encoded_jwt}") -resp = requests.post(f"http://ghe.pot8o.site/api/v3/app/installations/26/access_tokens", headers = { +resp = requests.post(f"http://ghe.pot8o.site/api/v3/app/installations/29/access_tokens", headers = { 'Accept' : 'application/vnd.github+json', 'Authorization' : f"Bearer {encoded_jwt}", 'X-GitHub-Api-Version' : '2022-11-28' From b3f7372e60b507179d83b22f71275f2eb419d028 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Thu, 26 Sep 2024 20:18:09 +0000 Subject: [PATCH 23/35] change how tokens are used --- config/__init__.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index ba7d12e..56ba4de 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -483,11 +483,21 @@ def __adoe_cloner_factory(api_session : APISession, config_path : str, config_di @staticmethod def __gh_cloner_factory(api_session : APISession, config_path : str, config_dict : Dict) -> Cloner: - ret_cloner = CxOneFlowConfig.__bitbucketdc_cloner_factory(api_session, config_path, config_dict) - if ret_cloner is None and 'app-private-key' in config_dict.keys(): - ret_cloner = Cloner.using_github_app_auth(GithubAppAuthFactory + if CxOneFlowConfig.__has_basic_auth(config_dict): + return Cloner.using_basic_auth(CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "username", config_dict), + CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "password", config_dict), True) + + if CxOneFlowConfig.__has_token_auth(config_dict): + return Cloner.using_basic_auth("git", CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "token", config_dict)) + + if CxOneFlowConfig.__has_ssh_auth(config_dict): + return Cloner.using_ssh_auth(Path(CxOneFlowConfig.__secret_root) / + Path(CxOneFlowConfig.__get_value_for_key_or_fail(config_path, "ssh", config_dict)), + config_dict['ssh-port'] if 'ssh-port' in config_dict.keys() else None) + + if 'app-private-key' in config_dict.keys(): + return Cloner.using_github_app_auth(GithubAppAuthFactory (CxOneFlowConfig.__get_secret_from_value_of_key_or_fail(config_path, "app-private-key", config_dict), api_session.api_endpoint)) - return ret_cloner @staticmethod From 37e9f14cfdd87beff46b60b6a5311d88da2f8426 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 27 Sep 2024 18:54:30 +0000 Subject: [PATCH 24/35] report unhandled events better --- orchestration/__init__.py | 20 ++++++++++++-------- orchestration/adoe.py | 4 ++++ orchestration/base.py | 5 +++++ orchestration/bbdc.py | 4 ++++ orchestration/gh.py | 5 ++++- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/orchestration/__init__.py b/orchestration/__init__.py index 2d6c950..9f3d423 100644 --- a/orchestration/__init__.py +++ b/orchestration/__init__.py @@ -3,7 +3,7 @@ from .adoe import AzureDevOpsEnterpriseOrchestrator from .gh import GithubOrchestrator import logging -from config import CxOneFlowConfig +from config import CxOneFlowConfig, RouteNotFoundException class OrchestrationDispatch: @@ -19,14 +19,18 @@ async def execute(orchestrator): if orchestrator.is_diagnostic: return 204 - OrchestrationDispatch.log().debug(f"Service lookup: {orchestrator.route_urls}") - cxone_service, scm_service, workflow_service = CxOneFlowConfig.retrieve_services_by_route(orchestrator.route_urls, orchestrator.config_key) - OrchestrationDispatch.log().debug(f"Service lookup success: {orchestrator.route_urls}") + try: + OrchestrationDispatch.log().debug(f"Service lookup: {orchestrator.route_urls}") + cxone_service, scm_service, workflow_service = CxOneFlowConfig.retrieve_services_by_route(orchestrator.route_urls, orchestrator.config_key) + OrchestrationDispatch.log().debug(f"Service lookup success: {orchestrator.route_urls}") + + if await orchestrator.is_signature_valid(scm_service.shared_secret): + return await orchestrator.execute(cxone_service, scm_service, workflow_service) + else: + OrchestrationDispatch.log().warning(f"Payload signature validation failed, webhook payload ignored.") + except RouteNotFoundException as ex: + OrchestrationDispatch.log().warning(f"Event [{orchestrator.event_name}] not handled for SCM [{orchestrator.config_key}]") - if await orchestrator.is_signature_valid(scm_service.shared_secret): - return await orchestrator.execute(cxone_service, scm_service, workflow_service) - else: - OrchestrationDispatch.log().warning(f"Payload signature validation failed, webhook payload ignored.") diff --git a/orchestration/adoe.py b/orchestration/adoe.py index 5ed9197..ce794fc 100644 --- a/orchestration/adoe.py +++ b/orchestration/adoe.py @@ -57,6 +57,10 @@ def __init__(self, event_context : EventContext): self.__collection = Path(urllib.parse.urlparse(self.__collection_url).path).name + @property + def event_name(self) -> str: + return self.__event + async def execute(self, cxone_service: CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): return await AzureDevOpsEnterpriseOrchestrator.__workflow_map[self.__event](self, cxone_service, scm_service, workflow_service) diff --git a/orchestration/base.py b/orchestration/base.py index 2c98940..fd2286d 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -32,6 +32,11 @@ def config_key(self): @property def event_context(self) -> EventContext: return self.__event_context + + @property + def event_name(self) -> str: + raise NotImplementedError("route_urls") + @property def route_urls(self) -> list: diff --git a/orchestration/bbdc.py b/orchestration/bbdc.py index 783a352..ee2ebee 100644 --- a/orchestration/bbdc.py +++ b/orchestration/bbdc.py @@ -54,6 +54,10 @@ def __init__(self, event_context : EventContext): self.__route_urls = list(self.__clone_urls.values()) + @property + def event_name(self) -> str: + return self.__event + @property def route_urls(self) -> list: diff --git a/orchestration/gh.py b/orchestration/gh.py index faa63a4..ac6e571 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -27,7 +27,7 @@ class GithubOrchestrator(OrchestratorBase): __push_target_branch_query = parse("$.ref") - __push_target_hash_query = parse("$.head_commit.id") + __push_target_hash_query = parse("$.after") __push_project_key_query = parse("$.repository.name") __push_org_key_query = parse("$.repository.owner.name") @@ -161,6 +161,9 @@ def __init__(self, event_context : EventContext): self.__clone_urls = GithubOrchestrator.__clone_url_parser_dispatch_map[self.__event](self) \ if self.__event in GithubOrchestrator.__clone_url_parser_dispatch_map.keys() else {} + @property + def event_name(self) -> str: + return self.__dispatch_event async def execute(self, cxone_service : CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): if self.__dispatch_event not in GithubOrchestrator.__workflow_map.keys(): From 816e6b7eb454add6acbd0d6428d3d5c445c3a841 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Fri, 27 Sep 2024 20:26:35 +0000 Subject: [PATCH 25/35] remove debug spam --- orchestration/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestration/base.py b/orchestration/base.py index fd2286d..e4f0be7 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -101,7 +101,7 @@ async def __exec_scan(self, cxone_service : CxOneService, scm_service : SCMServi with zipfile.ZipFile(zip_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as upload_payload: zip_entries = OrchestratorBase.__get_path_dict(code_path) - OrchestratorBase.log().debug(f"[{clone_url}][{source_branch}][{source_hash}] zipping for scan: {zip_entries}") + OrchestratorBase.log().debug(f"[{clone_url}][{source_branch}][{source_hash}] zipped {len(zip_entries)} files for scan.") for entry_key in zip_entries.keys(): upload_payload.write(entry_key, zip_entries[entry_key]) From 5235cdfe86a70f6ea1705f23b19156c10201a78d Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Mon, 30 Sep 2024 17:20:52 +0000 Subject: [PATCH 26/35] add display base for github-type scenarios --- config/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index 56ba4de..38ed95e 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -396,9 +396,12 @@ def __setup_scm(cloner_factory, api_auth_factory, scm_class, config_dict, config api_auth_dict = CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'api-auth', connection_config_dict) - display_url = CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict) - api_url = APISession.form_api_endpoint(CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict), + api_base_url = CxOneFlowConfig.__get_value_for_key_or_fail(f"{config_path}/connection", 'base-url', connection_config_dict) + + display_url = CxOneFlowConfig.__get_value_for_key_or_default('base-display-url', connection_config_dict, api_base_url) + + api_url = APISession.form_api_endpoint(api_base_url, CxOneFlowConfig.__get_value_for_key_or_default('api-url-suffix', connection_config_dict, None)) api_session = APISession(api_url, \ From 9c1c654460e29f587e2ad1658efc6424d6142482 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Mon, 30 Sep 2024 20:21:18 +0000 Subject: [PATCH 27/35] bug --- orchestration/base.py | 2 +- orchestration/gh.py | 2 +- scm_services/cloner.py | 2 +- wsgi.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/orchestration/base.py b/orchestration/base.py index e4f0be7..0f3d9f2 100644 --- a/orchestration/base.py +++ b/orchestration/base.py @@ -89,7 +89,7 @@ async def __exec_scan(self, cxone_service : CxOneService, scm_service : SCMServi clone_auth_fails = 0 while clone_auth_fails <= 1: try: - async with await (await self._get_clone_worker(scm_service, clone_url, clone_auth_fails)) as clone_worker: + async with await self._get_clone_worker(scm_service, clone_url, clone_auth_fails) as clone_worker: code_path = await clone_worker.loc() await scm_service.cloner.reset_head(code_path, source_hash) diff --git a/orchestration/gh.py b/orchestration/gh.py index ac6e571..c99793e 100644 --- a/orchestration/gh.py +++ b/orchestration/gh.py @@ -172,7 +172,7 @@ async def execute(self, cxone_service : CxOneService, scm_service : SCMService, return await GithubOrchestrator.__workflow_map[self.__dispatch_event](self, cxone_service, scm_service, workflow_service) async def _get_clone_worker(self, scm_service : SCMService, clone_url : str, failures : int) -> CloneWorker: - return scm_service.cloner.clone(clone_url, self.event_context, failures > 0) + return await scm_service.cloner.clone(clone_url, self.event_context, failures > 0) async def _get_target_branch_and_hash(self) -> tuple: return self.__target_branch, self.__target_hash diff --git a/scm_services/cloner.py b/scm_services/cloner.py index 2876075..cf0ae45 100644 --- a/scm_services/cloner.py +++ b/scm_services/cloner.py @@ -141,7 +141,7 @@ def destination_port(self): async def _get_clone_cmd_stub(self, event_context : Dict=None, api_url : str=None, force_reauth : bool=False) -> List: return self.__clone_cmd_stub - async def clone(self, clone_url, event_context : EventContext=None, force_reauth : bool=False): + async def clone(self, clone_url, event_context : EventContext=None, force_reauth : bool=False) -> CloneWorker: Cloner.log().debug(f"Clone Execution for: {clone_url}") fixed_clone_url = await self._fix_clone_url(clone_url, event_context, force_reauth) diff --git a/wsgi.py b/wsgi.py index 08a0624..9fc5bf3 100644 --- a/wsgi.py +++ b/wsgi.py @@ -45,7 +45,7 @@ async def bbdc_webhook_endpoint(): __log.info("Received hook for BitBucket Data Center") __log.debug(f"bbdc webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - TaskManager.in_background(OrchestrationDispatch.execute(BitBucketDataCenterOrchestrator(EventContext(request.data, request.headers)))) + TaskManager.in_background(OrchestrationDispatch.execute(BitBucketDataCenterOrchestrator(EventContext(request.get_data(), request.headers)))) return Response(status=204) except Exception as ex: __log.exception(ex) @@ -56,7 +56,7 @@ async def github_webhook_endpoint(): __log.info("Received hook for Github") __log.debug(f"github webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - orch = GithubOrchestrator(HeaderFilteredEventContext(request.data, request.headers, "User-Agent|X-(Git)?[H|h]ub")) + orch = GithubOrchestrator(HeaderFilteredEventContext(request.get_data(), request.headers, "User-Agent|X-(Git)?[H|h]ub")) if not orch.is_diagnostic: TaskManager.in_background(OrchestrationDispatch.execute(orch)) @@ -77,7 +77,7 @@ async def adoe_webhook_endpoint(): __log.info("Received hook for Azure DevOps Enterprise") __log.debug(f"adoe webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - orch = AzureDevOpsEnterpriseOrchestrator(EventContext(request.data, request.headers)); + orch = AzureDevOpsEnterpriseOrchestrator(EventContext(request.get_data(), request.headers)) if not orch.is_diagnostic: TaskManager.in_background(OrchestrationDispatch.execute(orch)) From 521542ea6568a1edd6afcb13a474c3f6c3598aa5 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Mon, 30 Sep 2024 21:32:57 +0000 Subject: [PATCH 28/35] adoe bug fix --- api_utils/auth_factories.py | 2 +- wsgi.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api_utils/auth_factories.py b/api_utils/auth_factories.py index 53f359f..d5d49c9 100644 --- a/api_utils/auth_factories.py +++ b/api_utils/auth_factories.py @@ -29,7 +29,7 @@ def __post_init__(self): class HeaderFilteredEventContext(EventContext): def __init__(self, raw_event_payload : str, headers : Dict, header_key_regex : str): pattern = re.compile(header_key_regex) - EventContext.__init__(self, raw_event_payload=raw_event_payload, headers={k:v for k,v in headers if pattern.match(k)}) + EventContext.__init__(self, raw_event_payload=raw_event_payload, headers={k:headers[k] for k in headers if pattern.match(k)}) diff --git a/wsgi.py b/wsgi.py index 9fc5bf3..835d03c 100644 --- a/wsgi.py +++ b/wsgi.py @@ -45,7 +45,7 @@ async def bbdc_webhook_endpoint(): __log.info("Received hook for BitBucket Data Center") __log.debug(f"bbdc webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - TaskManager.in_background(OrchestrationDispatch.execute(BitBucketDataCenterOrchestrator(EventContext(request.get_data(), request.headers)))) + TaskManager.in_background(OrchestrationDispatch.execute(BitBucketDataCenterOrchestrator(EventContext(request.get_data(), dict(request.headers))))) return Response(status=204) except Exception as ex: __log.exception(ex) @@ -56,7 +56,7 @@ async def github_webhook_endpoint(): __log.info("Received hook for Github") __log.debug(f"github webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - orch = GithubOrchestrator(HeaderFilteredEventContext(request.get_data(), request.headers, "User-Agent|X-(Git)?[H|h]ub")) + orch = GithubOrchestrator(HeaderFilteredEventContext(request.get_data(), dict(request.headers), "User-Agent|X-(Git)?[H|h]ub")) if not orch.is_diagnostic: TaskManager.in_background(OrchestrationDispatch.execute(orch)) @@ -77,7 +77,7 @@ async def adoe_webhook_endpoint(): __log.info("Received hook for Azure DevOps Enterprise") __log.debug(f"adoe webhook: headers: [{request.headers}] body: [{json.dumps(request.json)}]") try: - orch = AzureDevOpsEnterpriseOrchestrator(EventContext(request.get_data(), request.headers)) + orch = AzureDevOpsEnterpriseOrchestrator(EventContext(request.get_data(), dict(request.headers))) if not orch.is_diagnostic: TaskManager.in_background(OrchestrationDispatch.execute(orch)) From 5b5e93245ff97456bd78090060b6d0df0fffb501 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 1 Oct 2024 14:13:33 +0000 Subject: [PATCH 29/35] prevent dupe services --- config/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 38ed95e..6970952 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -178,7 +178,10 @@ def bootstrap(config_file_path = "./config.yaml"): repo_config_dict, f"/{scm}[{index}]") scm_tuple = (repo_matcher, cxone_service, scm_service, workflow_service_client) - CxOneFlowConfig.__scm_config_tuples_by_service_moniker[scm_service.moniker] = scm_tuple + if scm_service.moniker not in CxOneFlowConfig.__scm_config_tuples_by_service_moniker.keys(): + CxOneFlowConfig.__scm_config_tuples_by_service_moniker[scm_service.moniker] = scm_tuple + else: + raise ConfigurationException(f"Service {scm_service.moniker} is defined more than once.") if not scm in CxOneFlowConfig.__ordered_scm_config_tuples: CxOneFlowConfig.__ordered_scm_config_tuples[scm] = [scm_tuple] From 7cfe14b8eddf39467d7f15451c35dfe4d3d55f93 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 1 Oct 2024 17:51:31 +0000 Subject: [PATCH 30/35] fix ado ssh clone for cloud/on-prem --- orchestration/adoe.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/orchestration/adoe.py b/orchestration/adoe.py index ce794fc..d139e9d 100644 --- a/orchestration/adoe.py +++ b/orchestration/adoe.py @@ -8,6 +8,7 @@ from pathlib import Path from cxone_api.scanning import ScanInspector from api_utils.auth_factories import EventContext +from cxone_api.util import json_on_ok class AzureDevOpsEnterpriseOrchestrator(OrchestratorBase): @@ -49,7 +50,7 @@ def __init__(self, event_context : EventContext): self.__event = [x.value for x in list(self.__payload_type_query.find(self.event_context.message))][0] self.__route_urls = [x.value for x in list(self.__remoteurl_query.find(self.event_context.message))] - self.__clone_url = self.__route_urls[0] + self.__remote_url = self.__route_urls[0] self.__default_branches = [OrchestratorBase.normalize_branch_name(x.value) for x in list(self.__push_default_branch_query.find(self.event_context.message))] self.__repo_key = [x.value for x in list(self.__repo_project_key_query.find(self.event_context.message))][0] self.__repo_slug = [x.value for x in list(self.__repo_slug_query.find(self.event_context.message))][0] @@ -62,6 +63,13 @@ def event_name(self) -> str: return self.__event async def execute(self, cxone_service: CxOneService, scm_service : SCMService, workflow_service : WorkflowStateService): + # Get clone urls from repo details since ADO doesn't include all clone protocols in the event. + repo_details = json_on_ok(await scm_service.exec("GET", f"/{self.__collection}/{self.__repo_key}/_apis/git/repositories/{self.__repo_slug}")) + http_clone_url = urllib.parse.urlparse(self.__remote_url) + self.__clone_urls = { + http_clone_url.scheme : self.__remote_url, + "ssh" : repo_details['sshUrl'] + } return await AzureDevOpsEnterpriseOrchestrator.__workflow_map[self.__event](self, cxone_service, scm_service, workflow_service) @property @@ -102,18 +110,20 @@ async def is_signature_valid(self, shared_secret): sent_secret = base64.b64decode(base64_payload).decode("utf-8").split(":")[-1:].pop() return sent_secret == shared_secret + def _repo_clone_url(self, cloner) -> str: + return self.__clone_urls[cloner.select_protocol_from_supported(self.__clone_urls.keys())] - def _repo_clone_url(self, cloner): - parsed_clone_url = urllib.parse.urlparse(self.__clone_url) + # def _repo_clone_url(self, cloner): + # parsed_clone_url = urllib.parse.urlparse(self.__remote_url) - if parsed_clone_url.scheme in cloner.supported_protocols: - return self.__clone_url + # if parsed_clone_url.scheme in cloner.supported_protocols: + # return self.__remote_url - protocol = cloner.supported_protocols[0] - port = cloner.destination_port + # protocol = cloner.supported_protocols[0] + # port = cloner.destination_port - return urllib.parse.urlunparse((protocol, f"{parsed_clone_url.netloc}{f":{port}" if port is not None else ""}", - parsed_clone_url.path, parsed_clone_url.params, parsed_clone_url.query, parsed_clone_url.fragment)) + # return urllib.parse.urlunparse((protocol, f"{parsed_clone_url.netloc}{f":{port}" if port is not None else ""}", + # parsed_clone_url.path, parsed_clone_url.params, parsed_clone_url.query, parsed_clone_url.fragment)) async def _get_protected_branches(self, scm_service : SCMService): return self.__default_branches @@ -125,7 +135,7 @@ async def _get_source_branch_and_hash(self) -> tuple: return self.__source_branch, self.__source_hash async def get_cxone_project_name(self) -> str: - p = CloneUrlParser("azure", self.__clone_url) + p = CloneUrlParser("azure", self.__remote_url) return f"{p.org}/{self._repo_project_key}/{self._repo_name}" async def __is_pr_draft(self) -> bool: From 8d99d057830f7e85da4ae7407b26d3493aa5ae31 Mon Sep 17 00:00:00 2001 From: Nathan Leach Date: Tue, 1 Oct 2024 17:52:19 +0000 Subject: [PATCH 31/35] code cleanup --- orchestration/adoe.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/orchestration/adoe.py b/orchestration/adoe.py index d139e9d..c2e6aab 100644 --- a/orchestration/adoe.py +++ b/orchestration/adoe.py @@ -113,18 +113,6 @@ async def is_signature_valid(self, shared_secret): def _repo_clone_url(self, cloner) -> str: return self.__clone_urls[cloner.select_protocol_from_supported(self.__clone_urls.keys())] - # def _repo_clone_url(self, cloner): - # parsed_clone_url = urllib.parse.urlparse(self.__remote_url) - - # if parsed_clone_url.scheme in cloner.supported_protocols: - # return self.__remote_url - - # protocol = cloner.supported_protocols[0] - # port = cloner.destination_port - - # return urllib.parse.urlunparse((protocol, f"{parsed_clone_url.netloc}{f":{port}" if port is not None else ""}", - # parsed_clone_url.path, parsed_clone_url.params, parsed_clone_url.query, parsed_clone_url.fragment)) - async def _get_protected_branches(self, scm_service : SCMService): return self.__default_branches From dd473e0fd7a5e8d1431fe2a15b22e2ac8bf1294c Mon Sep 17 00:00:00 2001 From: nleach999 Date: Wed, 2 Oct 2024 16:28:46 -0500 Subject: [PATCH 32/35] doc updates --- README.md | 4 +- manual/appendix/troubleshooting.tex | 3 + manual/cxone-flow.tex | 3 +- manual/graphics/cxoneflow-deployment.png | Bin 65845 -> 65805 bytes manual/graphics/cxoneflow-diagrams.drawio | 230 +++++++++--------- manual/graphics/gh-app-cfg-1.png | Bin 0 -> 23557 bytes manual/graphics/gh-app-cfg-2.png | Bin 0 -> 20538 bytes manual/graphics/gh-app-cfg-3.png | Bin 0 -> 42301 bytes manual/graphics/gh-app-cfg-4.png | Bin 0 -> 68941 bytes manual/graphics/gh-app-cfg-5.png | Bin 0 -> 16232 bytes manual/graphics/gh-app-cfg-6.png | Bin 0 -> 19309 bytes manual/graphics/gh-new-app.png | Bin 0 -> 14410 bytes manual/operation/configuration.tex | 89 ++++--- manual/operation/ghc_app_minimal_example.tex | 25 ++ .../operation/ghc_webhook_minimal_example.tex | 25 ++ manual/operation/ghe_app_minimal_example.tex | 24 ++ .../operation/ghe_webhook_minimal_example.tex | 25 ++ manual/operation/overview.tex | 26 +- manual/operation/quickstart.tex | 11 +- manual/operation/yaml_anchors_example.tex | 14 +- manual/operation/yaml_full_example.tex | 14 +- manual/operation/yaml_minimal_example.tex | 94 ++++--- manual/scms/bbdc.tex | 34 ++- manual/scms/gh.tex | 128 ++++++++++ 24 files changed, 523 insertions(+), 226 deletions(-) create mode 100644 manual/graphics/gh-app-cfg-1.png create mode 100644 manual/graphics/gh-app-cfg-2.png create mode 100644 manual/graphics/gh-app-cfg-3.png create mode 100644 manual/graphics/gh-app-cfg-4.png create mode 100644 manual/graphics/gh-app-cfg-5.png create mode 100644 manual/graphics/gh-app-cfg-6.png create mode 100644 manual/graphics/gh-new-app.png create mode 100644 manual/operation/ghc_app_minimal_example.tex create mode 100644 manual/operation/ghc_webhook_minimal_example.tex create mode 100644 manual/operation/ghe_app_minimal_example.tex create mode 100644 manual/operation/ghe_webhook_minimal_example.tex create mode 100644 manual/scms/gh.tex diff --git a/README.md b/README.md index 3435b43..6c1bd37 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Please refer to the [Releases](https://github.com/checkmarx-ts/cxone-flow/releas * Supported SCMs * BitBucket Data Center * Azure DevOps Enterprise + * GitHub Enterprise and Cloud * Scans are invoked by Push events when code is pushed to protected branches. * Scans are invoked on Pull-Requests that target a protected branch. * Scan results for Pull-Request scans are summarized in a pull-request comment. -* Pull-Request state is reflected in scan tags as the pull request is under -review. +* Pull-Request state is reflected in scan tags as the pull request is under review. diff --git a/manual/appendix/troubleshooting.tex b/manual/appendix/troubleshooting.tex index aeeb5ae..7964d93 100644 --- a/manual/appendix/troubleshooting.tex +++ b/manual/appendix/troubleshooting.tex @@ -18,6 +18,9 @@ \chapter{Troubleshooting} image. A shell on the container can be opened using a command such as \texttt{docker exec -it bash} + \item For Git cloning issues, the setting environment variables \texttt{GIT\_TRACE=1} and + \texttt{GIT\_CURL\_VERBOSE=1} may log useful troubleshooting information. + \end{itemize} \noindent\\Exception stack traces emitted in the \cxoneflow logs can generally diff --git a/manual/cxone-flow.tex b/manual/cxone-flow.tex index 7df2012..e265f2f 100644 --- a/manual/cxone-flow.tex +++ b/manual/cxone-flow.tex @@ -76,8 +76,9 @@ \part{Operation} \part{Source Control Manager Configuration}\label{part:scms} \input{scms/common.tex} -\input{scms/bbdc.tex} \input{scms/adoe.tex} +\input{scms/bbdc.tex} +\input{scms/gh.tex} \part{Feedback Workflows}\label{part:feedback-workflows} \input{workflows/all.tex} diff --git a/manual/graphics/cxoneflow-deployment.png b/manual/graphics/cxoneflow-deployment.png index 77b5a2fdb1823c601eb2ea1485c635612c813d0f..c4abf2d9e62e25224979ffe7605a7032b0017ae0 100644 GIT binary patch literal 65805 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


S)QmX^+V#huh1;Q8kb3u<1-5i@9dA?f!42G@(Ir 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
}=2BO=LAsJ8nC87ZY!JW{1o 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 diff --git a/manual/graphics/cxoneflow-diagrams.drawio b/manual/graphics/cxoneflow-diagrams.drawio index 1d8ad04..1ee3f48 100644 --- a/manual/graphics/cxoneflow-diagrams.drawio +++ b/manual/graphics/cxoneflow-diagrams.drawio @@ -1,91 +1,61 @@ - + - + - + - + - + - - + + - + - + - + - - + + - - - - - - - - - - - - - - - - - - - - + + - - + + - - - - - - - - - - - - - - - - - + + - + - + + + + - + - + - + @@ -100,124 +70,146 @@ - + - - + + - - + + - + - - + + - - - + + - + - - + + - - + + - + - - + + - - - + + - + - - + + - - - - + + - + - - + + - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - + + - - + + - + - - + + - - + + - + + + + - - + + - - + + - + - - + + - - + + - + - - + + - - + + diff --git a/manual/graphics/gh-app-cfg-1.png b/manual/graphics/gh-app-cfg-1.png new file mode 100644 index 0000000000000000000000000000000000000000..13b51f10881b9f266d113bf26ecc79c020643d89 GIT binary patch literal 23557 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 WAnjfyiJt$upri3Xz4SgZ?7sp0adN)^ literal 0 HcmV?d00001 diff --git a/manual/graphics/gh-app-cfg-2.png b/manual/graphics/gh-app-cfg-2.png new file mode 100644 index 0000000000000000000000000000000000000000..048bfd8253fbe1c843045f6175adc774f11d66fd GIT binary patch literal 20538 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 XPqbO?X0|mo00000NkvXXu0mjfm(3(# literal 0 HcmV?d00001 diff --git a/manual/graphics/gh-app-cfg-4.png b/manual/graphics/gh-app-cfg-4.png new file mode 100644 index 0000000000000000000000000000000000000000..6af515dbb8fc651d230d0412137e5d3ff9beeab4 GIT binary patch literal 68941 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 literal 0 HcmV?d00001 diff --git a/manual/graphics/gh-app-cfg-5.png b/manual/graphics/gh-app-cfg-5.png new file mode 100644 index 0000000000000000000000000000000000000000..cd281741d6b2c45ba2b7104a937133ba2bac9a01 GIT binary patch literal 16232 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 WDEmB10(6x;jEsbWc(thE&;JdqwyAyq literal 0 HcmV?d00001 diff --git a/manual/graphics/gh-app-cfg-6.png b/manual/graphics/gh-app-cfg-6.png new file mode 100644 index 0000000000000000000000000000000000000000..677881df64f1eaf03aabc37fcae1cb0eca60533d GIT binary patch literal 19309 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 Yl86ZPp-u9LbAW&k?-e9V-x~S<52-$Fod5s; literal 0 HcmV?d00001 diff --git a/manual/operation/configuration.tex b/manual/operation/configuration.tex index 2194198..1cdd649 100644 --- a/manual/operation/configuration.tex +++ b/manual/operation/configuration.tex @@ -122,7 +122,9 @@ \subsubsection{YAML Root}\label{sec:yaml-root} \begin{itemize} \item \texttt{bbdc} for BitBucket Data Center webhook payloads targeting the \texttt{/bbdc} webhook payload receiver endpoint. - \item \texttt{adoe} for Azure DevOps Enterprise webhook payloads targeting the \texttt{/adoe} + \item \texttt{adoe} for Azure DevOps Enterprise or Cloud webhook payloads targeting the \texttt{/adoe} + webhook payload receiver endpoint. + \item \texttt{gh} for GitHub Enterprise or Cloud webhook payloads targeting the \texttt{/gh} webhook payload receiver endpoint. \end{itemize} @@ -435,8 +437,18 @@ \subsubsection{YAML SCM Moniker Configuration Elements}\label{sec:scm-block-elem \toprule \textbf{Key} & \textbf{Required} & \textbf{Default} & \textbf{Description}\\ \midrule - \texttt{base-url} & Yes & N/A & \makecell[l]{The base url of the SCM server.}\\ + \texttt{base-url} & Yes & N/A & \makecell[l]{The base url of the SCM server's API endpoint.\\This should be the + root URL for the API that can\\be used when composing all API calls related to the\\source of the received webhook event}\\ + \midrule + + \texttt{base-display-url} & No & \texttt{base-url} & \makecell[l]{An optional URL for use when composing links as + \\part of an information display such as pull-\\request feedback. Most SCMs will not require this\\setting.}\\ + \midrule + + \texttt{api-url-suffix} & No & N/A & \makecell[l]{An optional API URL suffix used when composing\\API request URLs. Most SCMs + will not require\\this setting.}\\ \midrule + \texttt{shared-secret} & Yes & N/A & \makecell[l]{The shared secret configured in the SCM used to sign\\webhook payloads. The shared secret must meet the\\following minimum criteria: 20 characters long,\\contains at least 3 numbers, contains at least\\3 upper-case letters, and contains at least 2 special\\characters.}\\ \midrule \texttt{timeout-seconds} & No & 60s & \makecell[l]{The number of seconds before a request for API\\results times out.}\\ @@ -455,15 +467,20 @@ \subsubsection{YAML SCM Moniker Configuration Elements}\label{sec:scm-block-elem \end{table} \pagebreak -\paragraph{YAML Configuration Element: \texttt{clone-auth} }\label{sec:clone-auth-element} -\noindent\\\\The \texttt{clone-auth} element is optional; if not provided, the connection information defined -in \texttt{api-auth} will be used. This element can contain the following key:value pair combinations: +\paragraph{YAML Configuration Element: \texttt{api-auth} }\label{sec:api-auth-element} + +\noindent\\\\The \texttt{api-auth} element is required. The authorization methods for \texttt{api-auth} +are used to communicate with the SCM's API and can often be used for cloning repositories. The +main difference between \texttt{api-auth} and \texttt{clone-auth} is that API access generally +does not support SSH authorization. If there is a need to clone using SSH, configure the SSH +authorization under the \texttt{clone-auth} element. This element can contain the following +key:value pair combinations: \noindent\\\textbf{Token Authorization Elements} -\noindent\\To clone with a token, the following elements can appear under the \texttt{clone-auth} -element exclusive of other elements: +\noindent\\To access the SCM API or clone with a token, the following elements can appear under the +\texttt{api-auth} element exclusive of other elements: \begin{itemize} \item \texttt{token} - The value specifies a file name found under the path defined @@ -471,13 +488,14 @@ \subsubsection{YAML SCM Moniker Configuration Elements}\label{sec:scm-block-elem token authorization. \item \texttt{username} - The value specifies a file name found under the path defined by \texttt{secret-root-path} containing a username associated with the PAT. This is - optional; if not supplied, the default username of \texttt{git} is used. + optional and only used during cloning; if not supplied, the default username of \texttt{git} is used. \end{itemize} \noindent\\\textbf{Basic Authorization Elements} -\noindent\\To clone with basic authorization, the following required elements can appear under the -\texttt{clone-auth} element exclusive of other elements: +\noindent\\To access the SCM API or clone with basic authorization\footnote{Many SCMs no longer support basic authorization.} +, the following required elements can +appear under the \texttt{api-auth} element exclusive of other elements: \begin{itemize} \item \texttt{username} - The value specifies a file name found under the path defined @@ -488,32 +506,29 @@ \subsubsection{YAML SCM Moniker Configuration Elements}\label{sec:scm-block-elem for authorization. \end{itemize} -\noindent\\\textbf{SSH Authorization Elements} +\noindent\\\textbf{Application Authorization Elements} -\noindent\\To clone with SSH, the following required element can appear under the -\texttt{clone-auth} element exclusive of other elements: +\noindent\\Application Authorization is available for use with select SCM types. When using Application Authorization, there is typically not a need +to provide a separate method of authorization for cloning defined in the \texttt{clone-auth} element. The following required +Application Authorization elements can appear under the \texttt{api-auth} element exclusive of other elements: \begin{itemize} - \item \texttt{ssh} - The value specifies a file name found under the path defined - by \texttt{secret-root-path} containing an unencrypted private SSH key. - \item \texttt{ssh-port} - This optional value specifies the port used for SSH cloning - if the SCM is not using port 22 and does not automatically include it in the clone - URL. + \item \texttt{app-private-key} - The value specifies a file name found under the path defined + by \texttt{secret-root-path} containing a private key used when obtaining application authorization. \end{itemize} -\paragraph{YAML Configuration Element: \texttt{api-auth} }\label{sec:api-auth-element} +\noindent\\Refer to Part \ref{part:scms} for details about SCMs that support this type of authorization. -\noindent\\\\The \texttt{api-auth} element is required. The authorization methods for \texttt{api-auth} -are used to communicate with the SCM's API and can often be used for cloning repositories. The -main difference between \texttt{api-auth} and \texttt{clone-auth} is that API access generally -does not support SSH authorization. If there is a need to clone using SSH, configure the SSH -authorization under the \texttt{clone-auth} element. This element can contain the following -key:value pair combinations: + +\paragraph{YAML Configuration Element: \texttt{clone-auth} }\label{sec:clone-auth-element} + +\noindent\\\\The \texttt{clone-auth} element is optional; if not provided, the connection information defined +in \texttt{api-auth} will be used. This element can contain the following key:value pair combinations: \noindent\\\textbf{Token Authorization Elements} -\noindent\\To access the SCM API or clone with a token, the following elements can appear under the -\texttt{api-auth} element exclusive of other elements: +\noindent\\To clone with a token, the following elements can appear under the \texttt{clone-auth} +element exclusive of other elements: \begin{itemize} \item \texttt{token} - The value specifies a file name found under the path defined @@ -521,13 +536,13 @@ \subsubsection{YAML SCM Moniker Configuration Elements}\label{sec:scm-block-elem token authorization. \item \texttt{username} - The value specifies a file name found under the path defined by \texttt{secret-root-path} containing a username associated with the PAT. This is - optional and only used during cloning; if not supplied, the default username of \texttt{git} is used. + optional; if not supplied, the default username of \texttt{git} is used. \end{itemize} \noindent\\\textbf{Basic Authorization Elements} -\noindent\\To access the SCM API or clone with basic authorization, the following required elements can -appear under the \texttt{api-auth} element exclusive of other elements: +\noindent\\To clone with basic authorization\footnote{Many SCMs no longer support basic authorization.}, the following required elements can appear under the +\texttt{clone-auth} element exclusive of other elements: \begin{itemize} \item \texttt{username} - The value specifies a file name found under the path defined @@ -538,6 +553,20 @@ \subsubsection{YAML SCM Moniker Configuration Elements}\label{sec:scm-block-elem for authorization. \end{itemize} +\noindent\\\textbf{SSH Authorization Elements} + +\noindent\\To clone with SSH, the following required element can appear under the +\texttt{clone-auth} element exclusive of other elements: + +\begin{itemize} + \item \texttt{ssh} - The value specifies a file name found under the path defined + by \texttt{secret-root-path} containing an unencrypted private SSH key. + \item \texttt{ssh-port} - This optional value specifies the port used for SSH cloning + if the SCM is not using port 22 and does not automatically include it in the clone + URL. +\end{itemize} + + diff --git a/manual/operation/ghc_app_minimal_example.tex b/manual/operation/ghc_app_minimal_example.tex new file mode 100644 index 0000000..9da57d6 --- /dev/null +++ b/manual/operation/ghc_app_minimal_example.tex @@ -0,0 +1,25 @@ +\begin{code}{Minimal YAML Configuration Example}{GitHub Cloud as GitHub App}{} +secret-root-path: /run/secrets +server-base-url: https://cxoneflow.mydomain.com:8443/ + +gh: + - service-name: GHC + repo-match: .* + feedback: + pull-request: + enabled: True + connection: + base-url: https://api.github.com + base-display-url: https://www.github.com + shared-secret: scm-shared-secret + api-auth: + app-private-key: ghe-app-priv-key + cxone: + tenant: mytenant + oauth: + client-id: my-oauth-id + client-secret: my-oauth-secret + iam-endpoint: US + api-endpoint: US +\end{code} + \ No newline at end of file diff --git a/manual/operation/ghc_webhook_minimal_example.tex b/manual/operation/ghc_webhook_minimal_example.tex new file mode 100644 index 0000000..0e272be --- /dev/null +++ b/manual/operation/ghc_webhook_minimal_example.tex @@ -0,0 +1,25 @@ +\begin{code}{Minimal YAML Configuration Example}{GitHub Cloud with Webhooks}{} +secret-root-path: /run/secrets +server-base-url: https://cxoneflow.mydomain.com:8443/ + +gh: + - service-name: GHE + repo-match: .* + feedback: + pull-request: + enabled: True + connection: + base-url: https://api.github.com + base-display-url: https://www.github.com + shared-secret: scm-shared-secret + api-auth: + token: ghc-token + cxone: + tenant: mytenant + oauth: + client-id: my-oauth-id + client-secret: my-oauth-secret + iam-endpoint: US + api-endpoint: US +\end{code} + \ No newline at end of file diff --git a/manual/operation/ghe_app_minimal_example.tex b/manual/operation/ghe_app_minimal_example.tex new file mode 100644 index 0000000..40ce8bf --- /dev/null +++ b/manual/operation/ghe_app_minimal_example.tex @@ -0,0 +1,24 @@ +\begin{code}{Minimal YAML Configuration Example}{GitHub Enterprise as GitHub App}{} +secret-root-path: /run/secrets +server-base-url: https://cxoneflow.mydomain.com:8443/ + +gh: + - service-name: GHE + repo-match: .* + feedback: + pull-request: + enabled: True + connection: + base-url: https://scm.corp.com + api-url-suffix: api/v3 + shared-secret: scm-shared-secret + api-auth: + app-private-key: ghe-app-priv-key + cxone: + tenant: mytenant + oauth: + client-id: my-oauth-id + client-secret: my-oauth-secret + iam-endpoint: US + api-endpoint: US +\end{code} diff --git a/manual/operation/ghe_webhook_minimal_example.tex b/manual/operation/ghe_webhook_minimal_example.tex new file mode 100644 index 0000000..408e51e --- /dev/null +++ b/manual/operation/ghe_webhook_minimal_example.tex @@ -0,0 +1,25 @@ +\begin{code}{Minimal YAML Configuration Example}{GitHub Enterprise with Webhooks}{} +secret-root-path: /run/secrets +server-base-url: https://cxoneflow.mydomain.com:8443/ + +gh: + - service-name: GHE + repo-match: .* + feedback: + pull-request: + enabled: True + connection: + base-url: https://scm.corp.com + api-url-suffix: api/v3 + shared-secret: scm-shared-secret + api-auth: + token: ghe-token + cxone: + tenant: mytenant + oauth: + client-id: my-oauth-id + client-secret: my-oauth-secret + iam-endpoint: US + api-endpoint: US +\end{code} + \ No newline at end of file diff --git a/manual/operation/overview.tex b/manual/operation/overview.tex index 4a31602..ddf7e9b 100644 --- a/manual/operation/overview.tex +++ b/manual/operation/overview.tex @@ -32,11 +32,27 @@ \section{Deployment Overview} systems, this can be done at a global or organization level. The web hooks will be configured to send events to a \cxoneflow endpoint specific to the type of source control system. -Figure \ref{fig:cxoneflow-deployment} depicts the way \cxoneflow is deployed. Some organizations may have -multiple source control systems of the same type or even multiple different types of source control systems. -There may be multiple Checkmarx One tenants or a combination of multi-tenant and single-tenant Checkmarx One -deployments. The \cxoneflow configuration allows each endpoint to be configured such that it orchestrates -scans in the correct Checkmarx One instance for the source of the web hook event. +Figure \ref{fig:cxoneflow-deployment} is a \cxoneflow deployment diagram. The key points of the diagram: + +\begin{itemize} + \item A single instance or clustered install of \cxoneflow can be used as the endpoint for multiple + SCM instances. + \item There may be more than one instance of an SCM type. + \item Each SCM instance may have one or more logical groups of repositories. For example: + \begin{itemize} + \item Azure DevOps has \textbf{Collections} and each collection has a \textbf{Project} where + each project will contain repositories. + \item GitHub has \textbf{Organizations} where each organization will contain repositories. + \item BitBucket Data Center has \textbf{Projects} where each project will contain repositories. + \end{itemize} + \item There may be multiple Checkmarx One tenants where scans are to be invoked from an SCM or SCM + logical group of repositories. +\end{itemize} + +The \cxoneflow configuration allows each endpoint to be configured such that it orchestrates +scans in the correct Checkmarx One instance and tenant for the SCM that emits the web hook event. +\cxoneflow is compatible with Checkmax One hosted single-tenant, hosted multi-tenant, and self-hosted +instances. \begin{figure}[ht] \includegraphics[width=\textwidth]{graphics/cxoneflow-deployment.png} diff --git a/manual/operation/quickstart.tex b/manual/operation/quickstart.tex index 264a3f7..dbf3dc7 100644 --- a/manual/operation/quickstart.tex +++ b/manual/operation/quickstart.tex @@ -5,16 +5,17 @@ \chapter{Quickstart} \section{Step 1: Create a basic configuration YAML file} -The full configuration documentation can be found in Section \ref{sec:op-config}. \input{operation/yaml_minimal_example.tex} - -\noindent\\To fully configure the YAML, you'll need the following information: +The full configuration documentation can be found in Section \ref{sec:op-config}. To fully configure the \cxoneflow you'll need +the following information that will be used when composing the configuration YAML file: \begin{itemize} \item A CheckmarxOne API Key or OAuth client id + client secret. - \item A username/password or Personal Access Token used to communicate with the Git server's API. - \item A username/password, Personal Access Token, or SSH private key used to clone Git repositories. + \item Credentials appropriate for communicating with the Git server's API. + \item Credentials used to clone Git repositories if the clone credentials must be different than the credentials used for API access. \end{itemize} +\noindent\\The types of credentials may vary based on how the SCM server is configured or how \cxoneflow +integrates with the SCM. \section{Step 2: Execute the \cxoneflow Container} diff --git a/manual/operation/yaml_anchors_example.tex b/manual/operation/yaml_anchors_example.tex index 5a7bf7a..18d9e06 100644 --- a/manual/operation/yaml_anchors_example.tex +++ b/manual/operation/yaml_anchors_example.tex @@ -17,15 +17,15 @@ repo-match: .* feedback: pull-request: - enabled: "True" + enabled: True scan-config: default-scan-engines: sca: - exploitablePath: "True" + exploitablePath: True sast: presetName: ASA Premium - incremental: "False" - fastScanMode: "True" + incremental: False + fastScanMode: True filter: "!**/node_modules,!**/test*" languageMode: multi kics: @@ -78,11 +78,11 @@ scan-config: default-scan-engines: &common-engine-config sca: - exploitablePath: "True" + exploitablePath: True sast: presetName: ASA Premium - incremental: "False" - fastScanMode: "True" + incremental: False + fastScanMode: True filter: "!**/node_modules,!**/test*" languageMode: multi kics: diff --git a/manual/operation/yaml_full_example.tex b/manual/operation/yaml_full_example.tex index a123730..72c4968 100644 --- a/manual/operation/yaml_full_example.tex +++ b/manual/operation/yaml_full_example.tex @@ -61,15 +61,15 @@ \pagebreak \noindent\\The next example shows a configuration where \cxoneflow has endpoint handlers for both -BitBucket Data Center and Azure DevOps Enterprise. Each SCM is configured to handle 3 distinct -projects to demonstrate Basic, Token, and SSH authentication methods. All the SCM endpoints +BitBucket Data Center and Azure DevOps Enterprise. Each SCM is configured to handle multiple distinct +projects to demonstrate the use of multiple authentication methods. All the SCM endpoints orchestrate scans in a single Checkmarx One tenant. \begin{code}{Multi-SCM YAML Configuration Example}{}{} secret-root-path: /run/secrets server-base-url: https://cxoneflow.mydomain.com:8443/ adoe-connection: &adoe-con - base-url: http://adoe.scm.org/DefaultCollection + base-url: http://adoe.scm.org/ shared-secret: scm-shared-secret bbdc-connection: &bbdc-con base-url: http://bbdc.scm.org @@ -102,8 +102,6 @@ connection: <<: *adoe-con api-auth: - token: adoe-token-secret - clone-auth: &basic-creds username: username-secret password: password-secret cxone: *cxone @@ -123,10 +121,4 @@ api-auth: token: bbdc-token-proj-pat cxone: *cxone - - service-name: BBDC-AsBasicAuth - repo-match: .*BAS - connection: - <<: *bbdc-con - api-auth: *basic-creds - cxone: *cxone \end{code} diff --git a/manual/operation/yaml_minimal_example.tex b/manual/operation/yaml_minimal_example.tex index 3185802..e3c3d27 100644 --- a/manual/operation/yaml_minimal_example.tex +++ b/manual/operation/yaml_minimal_example.tex @@ -29,18 +29,18 @@ base-url: https://scm.corp.com shared-secret: scm-shared-secret api-auth: - token: scm-token-secret + token: scm-token-secret cxone: tenant: mytenant oauth: - client-id: my-oauth-id - client-secret: my-oauth-secret + client-id: my-oauth-id + client-secret: my-oauth-secret iam-endpoint: US api-endpoint: US \end{code} \pagebreak -\noindent\\An alternate minimal example using different authorization options: +\noindent\\An alternate minimal example using a token for API authorization and SSH for cloning authorization: \begin{code}{Minimal YAML Configuration Example \#2}{Using BitBucket Data Center}{} secret-root-path: /run/secrets @@ -53,10 +53,9 @@ base-url: https://scm.corp.com shared-secret: scm-shared-secret api-auth: - username: scm-username-secret - password: scm-password-secret + token: scm-token-secret clone-auth: - ssh: scm-ssh-key-secret + ssh: scm-ssh-key-secret cxone: tenant: mytenant api-key: my-cxone-api-key @@ -76,13 +75,13 @@ - service-name: MyADO repo-match: .* connection: - base-url: https://scm.corp.com/DefaultCollection + base-url: https://scm.corp.com/ shared-secret: scm-shared-secret api-auth: - username: scm-username-secret - password: scm-password-secret + username: scm-username-secret + password: scm-password-secret clone-auth: - ssh: scm-ssh-key-secret + ssh: scm-ssh-key-secret cxone: tenant: mytenant api-key: my-cxone-api-key @@ -90,28 +89,55 @@ api-endpoint: US \end{code} +\pagebreak +\noindent\\A minimal example for BitBucket Data Center with pull-request feedback enabled: \begin{code}{Minimal YAML Configuration Example}{Enabling Pull Request Feedback}{} - secret-root-path: /run/secrets - server-base-url: https://cxoneflow.mydomain.com:8443/ - - bbdc: - - service-name: BitBucket DC - repo-match: .* - feedback: - pull-request: - enabled: "True" - connection: - base-url: https://scm.corp.com - shared-secret: scm-shared-secret - api-auth: - token: scm-token-secret - cxone: - tenant: mytenant - oauth: - client-id: my-oauth-id - client-secret: my-oauth-secret - iam-endpoint: US - api-endpoint: US - \end{code} - \ No newline at end of file +secret-root-path: /run/secrets +server-base-url: https://cxoneflow.mydomain.com:8443/ + +bbdc: + - service-name: BitBucket DC + repo-match: .* + feedback: + pull-request: + enabled: True + connection: + base-url: https://scm.corp.com + shared-secret: scm-shared-secret + api-auth: + token: scm-token-secret + cxone: + tenant: mytenant + oauth: + client-id: my-oauth-id + client-secret: my-oauth-secret + iam-endpoint: US + api-endpoint: US +\end{code} + +\pagebreak +\noindent\\A minimal example using GitHub Enterprise with the \cxoneflow integration installed +as a GitHub App: +\input{operation/ghe_app_minimal_example.tex} + + +\pagebreak +\noindent\\A minimal example using GitHub Enterprise with the \cxoneflow integration using +webhooks configured at the individual repository level: +\input{operation/ghe_webhook_minimal_example.tex} + + + + +\pagebreak +\noindent\\A minimal example using GitHub Cloud with the \cxoneflow integration installed +as a GitHub App: +\input{operation/ghc_app_minimal_example.tex} + + + +\pagebreak +\noindent\\A minimal example using GitHub Cloud with the \cxoneflow integration using +webhooks configured at the individual repository level: +\input{operation/ghc_webhook_minimal_example.tex} diff --git a/manual/scms/bbdc.tex b/manual/scms/bbdc.tex index 7c505b5..4f0940c 100644 --- a/manual/scms/bbdc.tex +++ b/manual/scms/bbdc.tex @@ -98,19 +98,21 @@ \section{Webhook Configuration} \section{\cxoneflow HTTP Access Tokens} -While it is possible to use Basic Authorization to access the SCM, typically this is a configuration that -should be avoided. The Basic Authorization is typically an interactive user account that can be subject -to password changes and Captcha verification that can break \cxoneflow operations. It is generally -best to use a project-level HTTP Access Token for SCM connection configurations \texttt{api-auth} or -\texttt{clone-auth}. Please refer to Section \ref{sec:connection-element} for more details about the token -configuration. - -Figure \ref{fig:bbdc-token-config} shows the project-level "HTTP Access tokens" configuration. The required +Current versions of BitBucket Data Center now reject the use of Basic Authorization using +a username and password. While it may be possible to use Basic Authorization to access the SCM, typically +this is a configuration that should be avoided. In addition to Basic Authorization breaking upon BitBucket Data Center +server upgrades, an interactive user account may be subject to password changes and Captcha verification that +can break \cxoneflow operations. It is generally best to use a project-level HTTP Access Token for SCM connection +configurations \texttt{api-auth} or \texttt{clone-auth}. Please refer to Section \ref{sec:connection-element} for more +details about the token configuration. + +\noindent\\Figure \ref{fig:bbdc-token-config} shows the project-level "HTTP Access tokens" configuration. The required token permissions for \cxoneflow operations are: \begin{itemize} \item Project read - \item Repository read\footnote{Figure \ref{fig:bbdc-token-config} shows "Repository write". There may be future versions of \cxoneflow that will need to create pull-request comments which will require write access. If desired, the token can be granted "Repository read" until a write capability is released.} + \item Repository read + \item Repository write \end{itemize} @@ -120,9 +122,17 @@ \section{\cxoneflow HTTP Access Tokens} \label{fig:bbdc-token-config} \end{figure} -Project-level access tokens do not have an associated user account. If using project-level -access tokens, one project-level token is required per-project for which \cxoneflow is -orchestrating scans. +\noindent\\Project-level access tokens do not have an associated user account that would be tied to +a user's access. If using project-level access tokens, one project-level token is required per-project +for which \cxoneflow is orchestrating scans. This also implies that there is one SCM service +configuration per project with an associated route match to use the correct project-level token. + +\noindent\\User-level access tokens are tied to a user account but are not subject to password +changes, 2FA authentication, and captcha challenges with interactive logins. A token generated +from a user as a service account can be granted the required permissions across all projects. +In this scenario, a single SCM service configuration would be able to handle events coming from +repositories in all projects. + \section{\cxoneflow SSH Keys} diff --git a/manual/scms/gh.tex b/manual/scms/gh.tex new file mode 100644 index 0000000..0d94835 --- /dev/null +++ b/manual/scms/gh.tex @@ -0,0 +1,128 @@ +\chapter{GitHub Enterprise and GitHub Cloud} + +\section{About GitHub} + +The GitHub integration supports self-hosted GitHub Enterprise or GitHub Cloud. The integration configurations +are virtually the same for each GitHub platform. Minor differences may exist due to license or subscription +limitations of your GitHub platform type. + +The GitHub integration, as with other SCM integrations, uses web hooks to emit events to the \cxoneflow endpoint. +GitHub has both organization-level and repository-level webhook configurations +that can be used for simple integration scenarios. \cxoneflow can also be configured as a GitHub Application +for easier deployment and visibility of deployment. +Deployment as a GitHub Application is the recommended deployment method. + + +\section{GitHub Application Configuration} + +\subsection{Configuring the Application Definition} + +The GitHub Application definition determines the owner of the application. The definition can +be created with an organization as owner or a user as owner. The configuration method is the same +for each type of ownership. Deploying with a user as application owner may be disruptive if the user +account is deleted or suspended. + +\noindent\\Once the type of ownership is decided, the GitHub Application configuration page can be found in the following +locations: + +\begin{itemize} + \item \textbf{Organization Ownership:} Organization->Settings>Developer Settings->GitHub Apps + \item \textbf{User Ownership:} User Icon (Top Right)->Settings->Developer Settings->GitHub Apps +\end{itemize} + +\noindent\\The following steps can be followed to configure the \cxoneflow GitHub App: + +\begin{enumerate} + \item To start the configuration, click the button "New GitHub App". Figure \ref{fig:gh-new-app} shows + the GitHub App page with the button to start the configuration in GitHub Enterprise. + \item Provide a name for the GitHub App as depicted in Figure \ref{fig:gh-app-cfg-1}. Any name + can be used, but \cxoneflow is suggested. + \item Optionally provide a description that is displayed to the users. + \item Provide a URL that can be used to find information about the \cxoneflow GitHub App. Any URL + can be used; the URL to the \cxoneflow GitHub page \textbf{https://github.com/checkmarx-ts/cxone-flow} + is suggested. + \item Scroll to the \textbf{Webhook} configuration section; the settings prior to the web hook configuration + are optional and not used by \cxoneflow. + \item Provide the URL to the \cxoneflow endpoint with the \textbf{/gh} suffix as shown in Figure \ref{fig:gh-app-cfg-2}. + \item Provide the web hook secret value that is configured in the \cxoneflow YAML as described in Section \ref{sec:connection-element}. + \item Scroll the the \textbf{Permissions} section and expand the \textbf{Repository Permissions} as shown in + Figure \ref{fig:gh-app-cfg-3}. The following permissions are required: + \begin{itemize} + \item Contents: Read-only + \item Pull requests: Read and write + \end{itemize} + \item Scroll to the \textbf{Subscribe to events} section as shown in Figure \ref{fig:gh-app-cfg-4}. The following events are required: + \begin{itemize} + \item Pull request + \item Pull request review + \item Push + \end{itemize} + \item Scroll to the \textbf{Where can this GitHub App be installed?} section and select \textbf{Any account} as shown in + Figure \ref{fig:gh-app-cfg-5}. + \item Click the \textbf{Create GitHub App} button. + \item After the application is created, open the application settings \textbf{General} tab and scroll to the + \textbf{Private keys} section as shown in Figure \ref{fig:gh-app-cfg-6}. + \item Click the \textbf{Generate a private key} button. A private key entry will appear and a file download will start. + The contents of the download file is the PEM encoded private key that is to be stored as a secret. See Section + \ref{sec:api-auth-element} for configuration instructions to reference this GitHub App private key. +\end{enumerate} + +\begin{figure}[ht] + \includegraphics[width=\textwidth]{graphics/gh-new-app.png} + \caption{New GitHub App Button} + \label{fig:gh-new-app} +\end{figure} + + +\begin{figure}[ht] + \includegraphics[width=\textwidth]{graphics/gh-app-cfg-1.png} + \caption{GitHub App Name and URL Configuration} + \label{fig:gh-app-cfg-1} +\end{figure} + +\begin{figure}[ht] + \includegraphics[width=\textwidth]{graphics/gh-app-cfg-2.png} + \caption{GitHub App Webhook Configuration} + \label{fig:gh-app-cfg-2} +\end{figure} + +\begin{figure}[ht] + \includegraphics[width=\textwidth]{graphics/gh-app-cfg-3.png} + \caption{GitHub App Permissions} + \label{fig:gh-app-cfg-3} +\end{figure} + +\begin{figure}[ht] + \includegraphics[width=\textwidth]{graphics/gh-app-cfg-4.png} + \caption{GitHub App Event Subscriptions} + \label{fig:gh-app-cfg-4} +\end{figure} + +\begin{figure}[ht] + \includegraphics[width=\textwidth]{graphics/gh-app-cfg-5.png} + \caption{GitHub App Install Scope} + \label{fig:gh-app-cfg-5} +\end{figure} + +\begin{figure}[ht] + \includegraphics[width=\textwidth]{graphics/gh-app-cfg-6.png} + \caption{GitHub App Private Keys} + \label{fig:gh-app-cfg-6} +\end{figure} + +\subsection{Deploying the Application} + + +\section{Webhook Configuration} + + +\section{Protected Branches} + +GitHub allows a default branch to be defined for each repository. A default branch name is defined at the organization +level and is initially inherited by each repository in the organization. The default branch may be changed in the repository +to any branch. If there are no branch protection rules for the repository generating a webhook event, the default branch for +the repository is assumed to be a protected branch. + +Branch protection rules can be defined at both the organization and repository levels. If there are any branches +defined as protected via the branch protection rules, those branches are considered protected branches. When branch protection +rules define protected branches, the repository default branch is not considered as a protected branch. From 46bff73eb11727eb9b4444060851d963d3e02ec4 Mon Sep 17 00:00:00 2001 From: nleach999 Date: Wed, 2 Oct 2024 16:35:10 -0500 Subject: [PATCH 33/35] release notes update --- manual/release_notes-content.tex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/manual/release_notes-content.tex b/manual/release_notes-content.tex index 4d20a27..edf1f0b 100644 --- a/manual/release_notes-content.tex +++ b/manual/release_notes-content.tex @@ -1,3 +1,10 @@ +\section{1.3} + +\begin{itemize} + \item Now supports GitHub Enterprise and GitHub Cloud + \item Documentation updates +\end{itemize} + \section{1.2} \begin{itemize} From cabf355a261d837d3b50607379354513584dcef8 Mon Sep 17 00:00:00 2001 From: nleach999 Date: Thu, 3 Oct 2024 10:43:57 -0500 Subject: [PATCH 34/35] manual updates --- manual/cxone-flow.tex | 1 + manual/graphics/gh-app-deploy-1.png | Bin 0 -> 15309 bytes manual/graphics/gh-app-deploy-2.png | Bin 0 -> 53491 bytes manual/graphics/gh-app-deploy-3.png | Bin 0 -> 32182 bytes manual/graphics/gh-app-deploy-4.png | Bin 0 -> 53680 bytes manual/graphics/gh-webhook-1.png | Bin 0 -> 25517 bytes manual/operation/ghc_app_minimal_example.tex | 2 +- manual/scms/gh.tex | 181 ++++++++++++++++++- manual/workflows/all.tex | 6 +- 9 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 manual/graphics/gh-app-deploy-1.png create mode 100644 manual/graphics/gh-app-deploy-2.png create mode 100644 manual/graphics/gh-app-deploy-3.png create mode 100644 manual/graphics/gh-app-deploy-4.png create mode 100644 manual/graphics/gh-webhook-1.png diff --git a/manual/cxone-flow.tex b/manual/cxone-flow.tex index e265f2f..ae8ac37 100644 --- a/manual/cxone-flow.tex +++ b/manual/cxone-flow.tex @@ -16,6 +16,7 @@ \usepackage{xparse} \usepackage{amssymb, amsmath} \usepackage[many]{tcolorbox} +\usepackage{placeins} \tcbuselibrary{listings} \hypersetup{ diff --git a/manual/graphics/gh-app-deploy-1.png b/manual/graphics/gh-app-deploy-1.png new file mode 100644 index 0000000000000000000000000000000000000000..76cb80d06199631e6d0a865b12bd7f67f72ff92e GIT binary patch literal 15309 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 literal 0 HcmV?d00001 diff --git a/manual/graphics/gh-app-deploy-3.png b/manual/graphics/gh-app-deploy-3.png new file mode 100644 index 0000000000000000000000000000000000000000..392b0842c8bd333f015baef23b8454bf372b213f GIT binary patch literal 32182 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

X@Eeo>4BS@=>ZR`-B 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 literal 0 HcmV?d00001 diff --git a/manual/graphics/gh-app-deploy-4.png b/manual/graphics/gh-app-deploy-4.png new file mode 100644 index 0000000000000000000000000000000000000000..4eded261952697a4127f593d090719c58cad5483 GIT binary patch literal 53680 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 literal 0 HcmV?d00001 diff --git a/manual/graphics/gh-webhook-1.png b/manual/graphics/gh-webhook-1.png new file mode 100644 index 0000000000000000000000000000000000000000..10ca0a2ce82c9d3937c6c383c571c938958c16f1 GIT binary patch literal 25517 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 f{uO>?&~g$ehx#hu=qR6k`Yl-X< Date: Thu, 3 Oct 2024 15:04:53 -0500 Subject: [PATCH 35/35] clarifying documentation --- manual/operation/configuration.tex | 22 ++++++++++++---------- manual/operation/deployment.tex | 16 ++++++++-------- manual/operation/quickstart.tex | 7 +++---- manual/operation/yaml_full_example.tex | 10 +++++++--- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/manual/operation/configuration.tex b/manual/operation/configuration.tex index 1cdd649..82b27d3 100644 --- a/manual/operation/configuration.tex +++ b/manual/operation/configuration.tex @@ -11,17 +11,17 @@ \subsubsection{Trusting Self-Signed Certificates}\label{sec:self-signed-certs} corporate proxies use certificates signed by a private CA. If so, it is possible to import custom CA certificates when using \cxoneflow. -\noindent\\The custom certificates must meet the following criteria: +\noindent\\Each custom certificate to import must meet the following criteria: \begin{itemize} - \item Must be in the PEM format. \item Must be in a file ending with the extension .crt. - \item Only one certificate is in the file. - \item Must be mapped to the container path /usr/local/share/ca-certificates. + \item The contents of the file must be one certificate stored in the PEM format. + \item Each file must be mapped to the container path /usr/local/share/ca-certificates. \end{itemize} -\noindent\\As an example, if using Docker, it is possible to map a local file to a file in the container with this mapping option added to the container execution command line: +\noindent\\As an example, if using Docker, it is possible to map a single local file to a file in the container with this mapping +option added to the container execution command line: \begin{code}{Custom CA Mapping Option}{[Docker]}{} -v $(pwd)/custom-ca.pem:/usr/local/share/ca-certificates/custom-ca.crt @@ -44,7 +44,8 @@ \subsubsection{The \texttt{ssl-verify} Option}\label{sec:ssl-verify-general} \subsubsection{Configuring SSL for the \cxoneflow Endpoint} To make the \cxoneflow endpoint use SSL for communication, obtain an SSL certificate public/private key pair -and then set the following environment variables in the runtime environment: +and map the files to a location on the container. The following environment variables must then be set in the +runtime environment: \begin{table}[ht] \caption{SSL Environment Variables} @@ -60,7 +61,7 @@ \subsubsection{Configuring SSL for the \cxoneflow Endpoint} \end{table} \noindent\\If your SSL certificate is self-signed or signed with a non-public CA, you'll want -to import the custom CA as described in Section \ref{sec:self-signed-certs}. +to import the self-signed certificate and/or non-public CA as described in Section \ref{sec:self-signed-certs}. \subsection{Runtime Control Environment Variables} @@ -71,7 +72,7 @@ \subsection{Runtime Control Environment Variables} \toprule \textbf{Variable} & \textbf{Required} & \textbf{Default} & \textbf{Description}\\ \midrule - \texttt{CXONEFLOW\_WORKERS} & No & \texttt{(\# of CPUs / 2)} & \makecell[l]{The number of worker processes\\used for parallel execution. The\\maximum value will be\\set at \texttt{(\# of CPUs - 1)}}\\ + \texttt{CXONEFLOW\_WORKERS} & No & \texttt{max(\# of CPUs / 2, 1)} & \makecell[l]{The number of worker processes\\used for parallel execution. The\\maximum value will be\\set at \texttt{(\# of CPUs - 1)}}\\ \midrule \texttt{LOG\_LEVEL} & No & \texttt{INFO} & \makecell[l]{The logging verbosity level. Set to\\\texttt{DEBUG} for increased logging\\verbosity.}\\ \midrule @@ -87,8 +88,9 @@ \subsection{Runtime Control Environment Variables} \section{Operational Configuration}\label{sec:op-config} -The operational configuration is done using a YAML file mapped at \texttt{/opt/cxone/config.yaml} -by default. +The operational configuration uses a YAML file mapped at \texttt{/opt/cxone/config.yaml} +by default. It is possible to map the \texttt{config.yaml} file to another location in the +container and adjust the path via the \texttt{CONFIG\_YAML\_PATH} environment variable. \subsection{YAML Configuration Examples} diff --git a/manual/operation/deployment.tex b/manual/operation/deployment.tex index 5e6b16c..aa218a2 100644 --- a/manual/operation/deployment.tex +++ b/manual/operation/deployment.tex @@ -59,12 +59,12 @@ \subsection{Hosting} \subsection{Scan Configuration Defaults}\label{sec:deployment-scan-defaults} Scan configuration defaults can be provided at the tenant-global and project scope -in CheckmarxOne. The \cxoneflow configuration can be configured to set configuration -defaults for each scan. It is important to understand how the CheckmarxOne default +in Checkmarx One. The \cxoneflow configuration can be configured to set configuration +defaults for each scan. It is important to understand how the Checkmarx One default scan configurations work given the \cxoneflow configuration can override all other settings based on how the tenant and project defaults are configured. -The configurations in CheckmarxOne are defined in the tenant or project scope with a +The configurations in Checkmarx One are defined in the tenant or project scope with a flag that allows the value to be overridden at a lower level of configuration. A SAST scan default preset, for example, is generally configured at the tenant scope as "ASA Premium" with the ability to override the preset at the project scope. The @@ -77,10 +77,10 @@ \subsection{Scan Configuration Defaults}\label{sec:deployment-scan-defaults} option for configuring the preset would not appear in the project settings. Configuring the SAST preset as a per-scan default in \cxoneflow will override both -the tenant and project scope settings if both are set to allow override. This is -due to the scan scope being the lowest level configuration scope. If a project scope -configuration was such that the SAST preset could not be overridden, then the \cxoneflow -default preset set at the time of scan will be ignored. +the tenant and project scope settings if both are set to allow override. The reason for this +is that the scan configuration provided to the API when a scan is initiated is considered to override +all other configuration scopes. If a project scope configuration was such that the SAST preset could +not be overridden, then the \cxoneflow provided default preset set at the time of scan will be ignored. The configurations at the tenant, project, and scan level scopes work based on overrides. This causes the value of a configuration to be set by the lowest level in which it is @@ -100,7 +100,7 @@ \subsection{Scan Configuration Defaults}\label{sec:deployment-scan-defaults} \subsubsection{\cxoneflow Inheritance of \texttt{filter} Settings} -The configuration scopes are implemented in CheckmarxOne as overrides for all +The configuration scopes are implemented in Checkmarx One as overrides for all configuration elements. This is ideal for most elements, but not ideal for elements such as \texttt{filter} that defines the file/folder filters in each engine. \cxoneflow interprets \texttt{filter} settings in each scan engine so that they are inherited diff --git a/manual/operation/quickstart.tex b/manual/operation/quickstart.tex index dbf3dc7..683d9d7 100644 --- a/manual/operation/quickstart.tex +++ b/manual/operation/quickstart.tex @@ -21,13 +21,12 @@ \section{Step 2: Execute the \cxoneflow Container} The published \cxoneflow container can be executed with the proper runtime configuration. Please refer to Section \ref{sec:runtime-config} for instructions about starting the \cxoneflow container instance with -the proper runtime configuration. +the proper runtime configuration. Section \ref{sec:deployment} has additional deployment information that +may be useful in choosing how and where to host the \cxoneflow instance. \section{Step 3: Configure SCM Webhooks} -Please refer to the chapter in Part \ref{part:scms} regarding your SCM's webhook configuration. +Please refer to the chapter for your SCM platform in Part \ref{part:scms} for details about webhook configuration. -\section{Step 4: Deploy} -Please see Section \ref{sec:deployment} for deployment instructions. diff --git a/manual/operation/yaml_full_example.tex b/manual/operation/yaml_full_example.tex index 72c4968..2e7c776 100644 --- a/manual/operation/yaml_full_example.tex +++ b/manual/operation/yaml_full_example.tex @@ -1,6 +1,5 @@ -This example shows a \cxoneflow configuration using all default options defined -as a handler when using a single SCM. This is the same as one of the simple examples if -an example had explicitly defined omitted default connection elements. +This example shows a \cxoneflow configuration explicitly setting default options in a service +configuration for a single SCM. The minimal examples leave several of these options as default. The \texttt{scan-config} element has been added to this configuration to demonstrate some of the controls that can be implemented over scan options. In this @@ -11,6 +10,10 @@ and described by \href{https://checkmarx.com/resource/documents/en/34965-68598-global-settings.html#UUID-8e38f06b-45d4-ea7f-5ff5-50deb22e43aa_UUID-1a4211ec-dbf9-a180-cb20-59e1246ec3fb}{Scanners Settings}. +While there are options to apply scan configurations via \texttt{scan-config} elements, it is often the case that defining the scan configuration +in \cxoneflow will have undesirable results. When defined in the \cxoneflow configuration, the configuration will explicitly override Checkmarx One +tenant and project level default scan configurations. Details about utilizing the Checkmarx One configuration options for best results with \cxoneflow +can be found in Section \ref{sec:deployment-scan-defaults}. \begin{code}{Full YAML Configuration Example}{[CxOne: oauth]}{[SCM: token auth]} secret-root-path: /run/secrets @@ -59,6 +62,7 @@ https: http://proxy.corp.com:8080 \end{code} + \pagebreak \noindent\\The next example shows a configuration where \cxoneflow has endpoint handlers for both BitBucket Data Center and Azure DevOps Enterprise. Each SCM is configured to handle multiple distinct