diff --git a/.release.yml b/.release.yml index 96a32d2..e355cb4 100644 --- a/.release.yml +++ b/.release.yml @@ -1,2 +1,3 @@ name: "ghas-reviewer-app" -version: "0.5.2" +repository: "advanced-security/ghas-reviewer-app" +version: "0.6.0" diff --git a/README.md b/README.md index 0782907..2c456a5 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,12 @@ The application is designed to be run in a container, this allows for easy deplo **Pull / Download image:** ```bash -# Pull latest (or a release) -docker pull ghcr.io/advanced-security/ghas-reviewer-app:main +# Pull latest or a release +docker pull ghcr.io/advanced-security/ghas-reviewer-app:latest + +or + +docker pull ghcr.io/advanced-security/ghas-reviewer-app:v0.6.0 ``` **Or Build From Source:** @@ -116,7 +120,7 @@ docker run \ --env-file=.env \ -v ./config:/ghasreview/config \ -p 9000:9000 \ - ghcr.io/advanced-security/ghas-reviewer-app:main + ghcr.io/advanced-security/ghas-reviewer-app:latest # or use release tag, example v0.6.0 ``` or run it locally diff --git a/ghasreview/__init__.py b/ghasreview/__init__.py index 0cf6fb5..7f2bac3 100644 --- a/ghasreview/__init__.py +++ b/ghasreview/__init__.py @@ -1,3 +1,3 @@ -__version__ = "0.5.0" +__version__ = "0.6.0" __url__ = "https://github.com/advanced-security/ghas-reviewer-app" diff --git a/ghasreview/__main__.py b/ghasreview/__main__.py index 4920f4e..5bd5fe5 100644 --- a/ghasreview/__main__.py +++ b/ghasreview/__main__.py @@ -2,4 +2,4 @@ if __name__ == "__main__": app = create_app(config) - app.run("0.0.0.0", port=9000, debug=True) + app.run("0.0.0.0", port=9000, debug=config.get("GHAS_DEBUG", False)) diff --git a/ghasreview/app.py b/ghasreview/app.py index f44d386..1ac3f10 100644 --- a/ghasreview/app.py +++ b/ghasreview/app.py @@ -132,48 +132,56 @@ def onCodeScanningAlertClose(): f"Processing Alert :: {alert.owner}/{alert.repository} => {alert.id} ({alert.ref})" ) + # Check if comment in alert + if current_app.config.get("GHAS_COMMENT_REQUIRED") and not alert.hasDismissedComment(): + logger.debug(f"Comment required, reopeneing alert: {alert.id}") + + open_alert = alert.client.reOpenCodeScanningAlert( + alert.owner, alert.repository, alert.id, + ) + if open_alert.status_code != 200: + logger.error(f"Unable to re-open alert :: {alert.id}") + logger.error("This might be a permissions issue, please check the documentation for more details") + return {"message": "Unable to re-open alert"} + return { + "message": "Comment required, re-opening alert" + } + # Check tool and severity tool = current_app.config.get("GHAS_TOOL") if tool and alert.tool != tool: logger.debug(f"Tool is not in the list of approved tools: {alert.tool}") return {"message": "Tool is not in the list of approved tools"} + + # Severity check, if not high enough, do not involve security team severities = current_app.config.get("GHAS_SEVERITIES") if severities and alert.severity not in severities: logger.debug( f"Severity is not high enough to get security involved: {alert.severity}" ) - return {"message": "Severity is not high enough to get security involved"} + return {"message": "Severity is not high enough to get security involved, doing nothing."} - # Check if an org account - # try: - # # TODO: Is this needed this org check? - # alert.client.organization(alert.owner) - # except Exception: - # logger.debug(f"Non-organization account is using the App, lets do nothing...") - # return {"message": "Non-organization account is using the App. Do nothing."} - - # Check team + # Check team exists if not alert.client.checkIfTeamExists(alert.owner, config.get("GHAS_TEAM")): - alert.createTeam(alert.owner, config.get("GHAS_TEAM")) - - # TODO: Check and Create Project Board - # process.createProjectBoard() + logger.info(f"GHAS Reviewer Team `{config.get('GHAS_TEAM')}` does not exist, creating team.") + alert.client.createTeam(alert.owner, config.get("GHAS_TEAM")) # check if the user is part of the security team - if alert.client.isUserPartOfTeam(alert.owner, alert.getUser()): + if alert.client.isUserPartOfTeam(alert.owner, config.get("GHAS_TEAM"), alert.getUser()): logger.debug(f"User is part of security team, no action taken.") return {"message": "User is part of the security team"} - logger.debug(f"User is not allowed to close alerts: {alert.getUser()}") + logger.info(f"User is not allowed to close alerts: {alert.getUser()} ({alert.owner}/{alert.repository} => {alert.id})") # Open Alert back up open_alert = alert.client.reOpenCodeScanningAlert( - alert.owner, alert.repository, alert.id + alert.owner, alert.repository, alert.id, ) if open_alert.status_code != 200: - logger.warning(f"Unable to re-open alert") - return + logger.error(f"Unable to re-open alert :: {alert.id}") + logger.error("This might be a permissions issue, please check the documentation for more details") + return {"message": "Unable to re-open alert"} if alert.isPR(): logger.debug(f"In PR request") @@ -183,7 +191,7 @@ def onCodeScanningAlertClose(): @app.errorhandler(500) def page_not_found(error): - data = {"error": 500} + data = {"error": 500, "msg": "Internal Server Error"} if app.debug: data["msg"] = str(error) resp = jsonify(**data) diff --git a/ghasreview/client.py b/ghasreview/client.py index 089fb3c..f7a4b90 100644 --- a/ghasreview/client.py +++ b/ghasreview/client.py @@ -26,7 +26,7 @@ def isUserPartOfTeam(self, owner_name: str, team_name: str, user: str) -> bool: f"{self.installation_client.session.base_url}/orgs/{owner_name}/teams/{team_name}", ) if team.status_code != 200: - logger.debug(f"Team does not exist :: {team_name} - {team.json()}") + logger.error(f"Team does not exist :: {team_name} - {team.json()}") return False membership_res = self.callApi( @@ -75,7 +75,7 @@ def createTeam(self, owner: str, team_name: str) -> bool: "description": "GitHub Advanced Security Reviewers", } team_creation = self.installation_client.session.post( - f"{self.base_url}/orgs/{owner}/teams", json=team_request + f"{self.installation_client.session.base_url}/orgs/{owner}/teams", json=team_request ) if team_creation.status_code != 200: logger.warning(f"Failed to create team :: {team_name} in {owner}") diff --git a/ghasreview/models/codescanning.py b/ghasreview/models/codescanning.py index 96a4226..f22d0fb 100644 --- a/ghasreview/models/codescanning.py +++ b/ghasreview/models/codescanning.py @@ -38,6 +38,10 @@ def ref(self) -> str: "ref" ) or self.payload.get("ref", "") + + def hasDismissedComment(self) -> bool: + return self.payload.get("dismissed_comment") is not None + @property def tool(self) -> str: return self.payload.get("alert", {}).get("tool", {}).get("name", "") diff --git a/ghasreview/setup.py b/ghasreview/setup.py index f7b798b..a5b04fe 100644 --- a/ghasreview/setup.py +++ b/ghasreview/setup.py @@ -25,6 +25,9 @@ def parse_arguments(): parser_github.add_argument( "--ghas-tool-name", default=os.environ.get("GITHUB_TOOL_NAME") or "CodeQL" ) + parser_github.add_argument( + "--ghas-comment-required", default=bool(os.environ.get("GITHUB_GHAS_COMMENT_REQUIRED", 0)) + ) parser_github = parser.add_argument_group("GitHub") parser_github.add_argument( @@ -59,6 +62,7 @@ def setup_logging(arguments): logging.info(f"GitHub Key Path :: {arguments.github_app_key_path}") logging.debug(f"GitHub App Secret :: {arguments.github_app_secret}") logging.debug(f"GHAS Tool Name :: {arguments.ghas_tool_name}") + logging.debug(f"GHAS Comment Required :: {arguments.ghas_comment_required}") def validate_arguments(arguments): @@ -89,11 +93,13 @@ def setup_app(): setup_logging(arguments) app_key = validate_arguments(arguments) config = { + "GHAS_DEBUG": arguments.debug, # Set the route "GITHUBAPP_ROUTE": arguments.github_app_endpoint, # Team name "GHAS_TEAM": arguments.ghas_team_name, "GHAS_BOARD_NAME": "GHAS Reviewers Audit Board", + "GHAS_COMMENT_REQUIRED": arguments.ghas_comment_required, # Tool and severities to check "GHAS_TOOL": arguments.ghas_tool_name, "GHAS_SEVERITIES": ["critical", "high", "error", "errors"],