defmodule Mix.Tasks.Paraxial.Scan do
use Mix.Task
require Logger
alias Paraxial.Scan
alias Paraxial.Helpers
@gh_app_args ["--github_app", "--install_id", "--repo_owner", "--repo_name", "--pr_number"]
@impl Mix.Task
def run(args) do
HTTPoison.start()
api_key = Helpers.get_api_key()
if api_key == nil do
Logger.error("[Paraxial] API key NOT found, scan results cannot be uploaded")
else
Logger.info("[Paraxial] API key found, scan results will be uploaded")
end
sobelow =
Task.async(fn ->
System.cmd("mix", ["sobelow", "--private", "--skip", "--config", "--format", "json"])
end)
deps_audit =
Task.async(fn ->
System.cmd("mix", ["deps.audit"])
end)
hex_audit =
Task.async(fn ->
System.cmd("mix", ["hex.audit"])
end)
{sobelow, _exit_code} = Task.await(sobelow, :infinity)
{deps_audit, _exit_code} = Task.await(deps_audit, :infinity)
{hex_audit, _exit_code} = Task.await(hex_audit, :infinity)
sl = Scan.make_sobelow(sobelow)
dl = Scan.make_deps(deps_audit)
hl = Scan.make_hex(hex_audit)
findings = List.flatten([sl, dl, hl])
scan = %Scan{
timestamp: Scan.get_timestamp(),
findings: findings,
api_key: "REDACTED"
}
IO.inspect(scan, label: "[Paraxial] Scan findings")
scan = Map.put(scan, :api_key, api_key)
json = Jason.encode!(scan)
url = Helpers.get_scan_ingest_url()
scan_info =
case HTTPoison.post(url, json, [{"Content-Type", "application/json"}]) do
{:ok, %{body: body}} ->
if String.contains?(body, "Scan written successfully") do
%{"ok" => scan_info} = Jason.decode!(body)
Logger.info("[Paraxial] #{scan_info}")
scan_info
else
Logger.error("[Paraxial] Scan upload failed, check configuration.")
:error
end
_ ->
Logger.error("[Paraxial] Scan upload failed, check configuration.")
:error
end
cond do
Enum.all?(@gh_app_args, fn a -> a in args end) and scan_info == :error ->
Logger.error("[Paraxial] Github upload did not run due to original scan upload failure.")
:ok
Enum.all?(@gh_app_args, fn a -> a in args end) ->
Logger.info("[Paraxial] Github App Correct Arguments")
github_app_upload(args, scan_info)
"--github_app" in args ->
Logger.error(
"[Paraxial] --github_app is missing arguments. Required: --install_id, --repo_owner, --repo_name, --pr_number"
)
true ->
:ok
end
if "--add-exit-code" in args and length(scan.findings) > 0 do
exit({:shutdown, 1})
end
end
def github_app_upload(args, scan_info) do
regex = ~r/UUID (.+)/
captures = Regex.run(regex, scan_info, capture: :all_but_first)
scan_uuid = Enum.at(captures, 0)
cli_map = args_to_map(args)
censored_backend_map = %{
"installation_id" => Map.get(cli_map, "--install_id"),
"repository_owner" => Map.get(cli_map, "--repo_owner"),
"repository_name" => Map.get(cli_map, "--repo_name"),
"pull_request_number" => Map.get(cli_map, "--pr_number"),
"scan_uuid" => scan_uuid,
"api_key" => "REDACTED"
}
IO.inspect(censored_backend_map, label: "[Paraxial] Github Upload info")
backend_map = Map.put(censored_backend_map, "api_key", Helpers.get_api_key())
url = Helpers.get_github_app_url()
json = Jason.encode!(backend_map)
debug_url =
"https://github.com/#{cli_map["--repo_owner"]}/#{cli_map["--repo_name"]}/pull/#{cli_map["--pr_number"]}"
case HTTPoison.post(url, json, [{"Content-Type", "application/json"}]) do
{:ok, %{body: body}} ->
if String.contains?(body, "Comment created successfully") do
Logger.info("[Paraxial] Github PR Comment Created successfully")
Logger.info("[Paraxial] URL: #{debug_url}")
else
Logger.error("[Paraxial] Github PR Comment failed")
end
_ ->
Logger.error("[Paraxial] Github PR Comment failed")
end
end
def args_to_map(args) do
Enum.reduce(args, %{prev_val: false}, fn arg, acc ->
cond do
arg in @gh_app_args ->
# Create a new key
acc
|> Map.put(:prev_val, arg)
acc[:prev_val] != false ->
# The previous flag in the list was valid, the current arg is the value
acc
|> Map.put(acc[:prev_val], arg)
|> Map.put(:prev_val, false)
true ->
# No valid flag or value for the flag, do nothing
acc
end
end)
|> Map.delete(:prev_val)
end
end