Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions .github/scripts/create-openvex-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ set -e # exit immediately if any command fails

REPO=$1
BRANCH_NAME=$2
ORPHAN_BRANCH="openvex"
OTP_REMOTE="${3:-origin}"

# Fetch PR data using gh CLI
PR_STATUS=$(gh pr view "$BRANCH_NAME" --repo "$REPO" --json state -q ".state")
FOUND_PR=$?
Expand All @@ -33,17 +36,32 @@ if [ "$FOUND_PR" -ne 0 ]; then
echo "A new PR will be created"
fi

# Check if PR is closed
if [ "$PR_STATUS" = "CLOSED" ] || [ "$PR_STATUS" = "MERGED" ] || [ "$FOUND_PR" -ne 0 ]; then
echo "Pull request #$BRANCH_NAME is CLOSED or MERGED."
echo "✅ A new pull request with name #$BRANCH_NAME will be created."
git branch "$BRANCH_NAME" master
git checkout "$BRANCH_NAME"
git add make/openvex.table
git add vex

# Stash the generated openvex files (including untracked ones)
git stash push --include-untracked -m "openvex generated files"

# Fetch and checkout the orphan branch
# OTP_REMOTE is `origin` when this script runs as part of otp-compliance.es called by openvex-sync.yaml
# OTP_REMOTE is `upstream` when we run this script as part of locally removing a false positive CVE,
# when running `otp-compliance vex run`.
git fetch "$OTP_REMOTE" "$ORPHAN_BRANCH"
git checkout "$ORPHAN_BRANCH"

# Create a new work branch from the orphan branch
git checkout -b "$BRANCH_NAME"

# Restore the generated files from the stash
git stash pop

git add otp-*.openvex.json
git add otp-*.openvex.json.license
git commit -m "Automatic update of OpenVEX Statements for erlang/otp"
git push --force origin "$BRANCH_NAME"
gh pr create --repo "$REPO" -B master \

gh pr create --repo "$REPO" -B "$ORPHAN_BRANCH" \
--title "Automatic update of OpenVEX Statements for erlang/otp" \
--body "Automatic Action. There is a vulnerability from GH Advisories without a matching OpenVEX statement"
else
Expand Down
102 changes: 61 additions & 41 deletions .github/scripts/otp-compliance.es
Original file line number Diff line number Diff line change
Expand Up @@ -104,24 +104,23 @@
-define(FOUND_VENDOR_VULNERABILITY_TITLE, "Vendor vulnerability found").
-define(FOUND_VENDOR_VULNERABILITY, lists:append(string:replace(?FOUND_VENDOR_VULNERABILITY_TITLE, " ", "+", all))).

-define(OTP_GH_URI, "https://raw.githubusercontent.com/" ++ ?GH_ACCOUNT ++ "/refs/heads/master/").
-define(OTP_GH_URI, "https://raw.githubusercontent.com/" ++ ?GH_ACCOUNT ++ "/refs/heads/openvex/").

%% GH default options
-define(GH_ADVISORIES_OPTIONS, "state=published&direction=desc&per_page=100&sort=updated").

%% Advisories to download from last X years.
-define(GH_ADVISORIES_FROM_LAST_X_YEARS, 5).

%% Defines path of script to create PRs for missing openvex/vulnerabilities
-define(CREATE_OPENVEX_PR_SCRIPT_FILE, ".github/scripts/create-openvex-pr.sh").

%% Sets end point account to fetch information from GH
%% used by `gh` command-line tool.
%% change to your fork for testing, e.g., `kikofernandez/otp`
-define(GH_ACCOUNT, "erlang/otp").
%%
%%

-define(DEBUG, false).

%% Add more relations if necessary.
-type spdx_relations() :: #{ 'DOCUMENTATION_OF' => [],
'CONTAINS' => [],
Expand Down Expand Up @@ -293,15 +292,35 @@ cli() ->
#{ help =>
"""
Initialise an openvex file.
1. Update the `make/openvex.table`
2. Run the command:

> .github/scripts/otp-compliance.es vex init -b otp-33

""",
arguments => [ input_option(~"make/openvex.table"), branch_option(), vex_path_option()],
arguments => [ input_option(~"make/openvex.table"), branch_option()],
handler => fun init_openvex/1},
"run" =>
#{ help =>
"""
Updates an openvex file.
Updates OpenVEX files.
1. Update the `make/openvex.table`
2. Run the following command, which downloads otp-*.openvex.json files
and updates those files using the `openvex.table`

> .github/scripts/otp-compliance.es vex run -b otp-33 | bash

3. Run the following script to create a PR.
The script stashes the generated openvex files.
Fetches the orphan branch `openvex`, and creates a new branch named `vex` locally.
Pops the stash and commits the generate OpenVEX files.
Pushes to `origin` (must be set to your fork).
Creates a PR towards `erlang/otp` with base `openvex`.

> .github/scripts/create-openvex-pr.sh erlang/otp vex upstream

""",
arguments => [ input_option(~"make/openvex.table"), branch_option(), vex_path_option()],
arguments => [ input_option(~"make/openvex.table"), branch_option()],
handler => fun run_openvex/1},

"verify" =>
Expand All @@ -313,6 +332,7 @@ cli() ->
Creates PR for any non-present Github Advisory.

Example:

> .github/scripts/otp-compliance.es vex verify -p

""",
Expand Down Expand Up @@ -490,14 +510,6 @@ branch_option() ->
short => $b,
long => "-branch"}.

vex_path_option() ->
#{name => vex_path,
type => binary,
required => false,
default => ?VexPath,
help => "Path to folder containing openvex statements, e.g., `vex/`",
long => "-vex-path"}.

create_pr() ->
#{name => create_pr,
short => $p,
Expand Down Expand Up @@ -1633,31 +1645,39 @@ format_vex_statements(OpenVex) ->
end, [], Stmts).

read_openvex_file(Branch) ->
_ = create_dir(?VexPath),
_ = download_otp_openvex_file(Branch),
OpenVexPath = path_to_openvex_filename(Branch),
OpenVexStr = erlang:binary_to_list(OpenVexPath),
decode(OpenVexStr).

dbg(Text, Args) ->
case ?DEBUG of
true ->
io:format(Text, Args);
false ->
ok
end.

-spec download_otp_openvex_file(Branch :: binary()) -> Json :: map() | EmptyMap :: #{} | no_return().
download_otp_openvex_file(Branch) ->
_ = create_dir(?VexPath),
OpenVexPath = path_to_openvex_filename(Branch),
OpenVexStr = erlang:binary_to_list(OpenVexPath),
GithubURI = get_gh_download_uri(OpenVexStr),

io:format("Checking OpenVex statements in '~s' from~n'~s'...~n", [OpenVexPath, GithubURI]),
dbg("Checking OpenVex statements in '~s' from~n'~s'...~n", [OpenVexPath, GithubURI]),

ValidURI = "curl -I -Lj --silent " ++ GithubURI ++ " | head -n1 | cut -d' ' -f2",
case string:trim(os:cmd(ValidURI)) of
"200" ->
%% Overrides existing file.
io:format("OpenVex file found.~n~n"),
dbg("OpenVex file found.~n~n", []),
Command = "curl -LJ " ++ GithubURI ++ " --output " ++ OpenVexStr,
io:format("Proceed to download:~n~s~n~n", [Command]),
dbg("Proceed to download:~n~s~n~n", [Command]),
os:cmd(Command, #{ exception_on_failure => true }),
decode(OpenVexStr);
E ->
io:format("[~p] No OpenVex statements found for file '~s'.~n~n", [E, OpenVexStr]),
dbg("[~p] No OpenVex statements found for file '~s'.~n~n", [E, OpenVexStr]),
#{}
end.

Expand All @@ -1670,7 +1690,7 @@ create_dir(DirName) ->
case file:make_dir(DirName) of
Result when Result == ok;
Result == {error, eexist} ->
io:format("Directory ~s created successfully.~n", [DirName]);
dbg("Directory ~s created successfully.~n", [DirName]);
{error, Reason} ->
fail("Failed to create directory ~s: ~p~n", [DirName, Reason])
end.
Expand Down Expand Up @@ -2801,14 +2821,12 @@ extracted_license_info() ->
%% Documentation in HOWTO/SBOM.md
%%


vex_path(Branch) ->
VexPath = ?VexPath,
vex_path(VexPath, Branch).
vex_path(VexPath, Branch) ->
<<VexPath/binary, Branch/binary, ".openvex.json">>.
<<Branch/binary, ".openvex.json">>.

init_openvex(#{input_file := File, branch := Branch, vex_path := VexPath}) ->
InitVex = vex_path(VexPath, Branch),
init_openvex(#{input_file := File, branch := Branch}) ->
InitVex = vex_path(Branch),
VexStmts = case filelib:is_file(InitVex) of
true -> % file exists
maps:get(~"statements", decode(InitVex));
Expand All @@ -2817,15 +2835,17 @@ init_openvex(#{input_file := File, branch := Branch, vex_path := VexPath}) ->
file:write_file(InitVex, json:format(Init)),
maps:get(~"statements", Init)
end,
run_openvex1(VexStmts, File, Branch, VexPath).
run_openvex1(VexStmts, File, Branch).

run_openvex(#{input_file := File, branch := Branch, vex_path := VexPath}) ->
InitVex = vex_path(VexPath, Branch),
run_openvex(#{input_file := File, branch := Branch}) ->
%% Download files from orphan branch into VexPath Folder.
_ = download_otp_openvex_file(Branch),
InitVex = vex_path(Branch),
VexStmts = maps:get(~"statements", decode(InitVex)),
run_openvex1(VexStmts, File, Branch, VexPath).
run_openvex1(VexStmts, File, Branch).

run_openvex1(VexStmts, VexTableFile, Branch, VexPath) ->
Statements = calculate_statements(VexStmts, VexTableFile, Branch, VexPath),
run_openvex1(VexStmts, VexTableFile, Branch) ->
Statements = calculate_statements(VexStmts, VexTableFile, Branch),
lists:foreach(fun (St) -> io:format("~ts", [St]) end, Statements).

verify_openvex(#{create_pr := PR}) ->
Expand Down Expand Up @@ -3169,14 +3189,14 @@ openvex_filter_product(Products) ->
vex_set_inclusion(AdvVEX, OpenVEX) ->
[VEX || VEX <- AdvVEX, not lists:member(VEX, OpenVEX)].

calculate_statements(VexStmts, VexTableFile, Branch, VexPath) ->
calculate_statements(VexStmts, VexTableFile, Branch) ->
VexTable = decode(VexTableFile),
case maps:get(Branch, VexTable, error) of
error ->
fail("Could not find '~ts' in file '~ts'.~nDid you forget to add an entry with name '~ts' into 'openvex.table'?",
[Branch, VexTableFile, Branch]);
CVEs ->
calculate_statements_from_cves(VexStmts, CVEs, Branch, VexPath)
calculate_statements_from_cves(VexStmts, CVEs, Branch)
end.

exists_cve_in_openvex(VexStmts, CVE, StatusCVE, Purl) ->
Expand Down Expand Up @@ -3210,7 +3230,7 @@ fetch_openvex_status(M) when is_map(M) ->
fetch_openvex_status(_) ->
{false, false}.

calculate_statements_from_cves(VexStmts, CVEs, Branch, VexPath) ->
calculate_statements_from_cves(VexStmts, CVEs, Branch) ->
%% make the function idempotent, i.e., can be called consecutive times producing the same input
lists:foldl(
fun (#{~"status" := Status}=M, Acc) ->
Expand All @@ -3220,7 +3240,7 @@ calculate_statements_from_cves(VexStmts, CVEs, Branch, VexPath) ->
true -> %% entry exists, ignore to make operation idempotent
Acc;
false ->
InitVex = vex_path(VexPath, Branch),
InitVex = vex_path(Branch),
{FixedStatus, AffectedStatus} = fetch_openvex_status(Status),
case Purl of
<<?ErlangPURL, _/binary>> ->
Expand Down Expand Up @@ -3547,9 +3567,9 @@ test_openvex(_) ->


test_openvex_branched_otp_tree() ->
{VexPath, Branch, VexStmts} = setup_openvex_test(),
{_VexPath, Branch, VexStmts} = setup_openvex_test(),
CVEs = fixup_openvex_branched_otp_tree(),
Result = calculate_statements_from_cves(VexStmts, CVEs, Branch, VexPath),
Result = calculate_statements_from_cves(VexStmts, CVEs, Branch),
Expected = [~"vexctl add --in-place otp-23.openvex.json --product='pkg:github/erlang/otp@OTP-23.0,pkg:github/erlang/otp@OTP-23.0.1,pkg:github/erlang/otp@OTP-23.0.2,pkg:github/erlang/otp@OTP-23.0.3,pkg:github/erlang/otp@OTP-23.0.4,pkg:otp/ssl@10.0,pkg:github/erlang/otp@OTP-23.1,pkg:github/erlang/otp@OTP-23.1.1,pkg:github/erlang/otp@OTP-23.1.2,pkg:github/erlang/otp@OTP-23.1.3,pkg:github/erlang/otp@OTP-23.1.4,pkg:github/erlang/otp@OTP-23.1.4.1,pkg:github/erlang/otp@OTP-23.1.5,pkg:otp/ssl@10.1,pkg:github/erlang/otp@OTP-23.2,pkg:github/erlang/otp@OTP-23.2.1,pkg:otp/ssl@10.2,pkg:github/erlang/otp@OTP-23.2.2,pkg:github/erlang/otp@OTP-23.2.3,pkg:otp/ssl@10.2.1,pkg:github/erlang/otp@OTP-23.2.4,pkg:otp/ssl@10.2.2,pkg:github/erlang/otp@OTP-23.2.5,pkg:github/erlang/otp@OTP-23.2.6,pkg:otp/ssl@10.2.3,pkg:github/erlang/otp@OTP-23.2.7,pkg:otp/ssl@10.2.4,pkg:github/erlang/otp@OTP-23.2.7.1,pkg:otp/ssl@10.2.4.1,pkg:github/erlang/otp@OTP-23.2.7.2,pkg:github/erlang/otp@OTP-23.2.7.3,pkg:otp/ssl@10.2.4.2,pkg:github/erlang/otp@OTP-23.2.7.4,pkg:otp/ssl@10.2.4.3,pkg:github/erlang/otp@OTP-23.2.7.5,pkg:otp/ssl@10.2.4.4,pkg:github/erlang/otp@OTP-23.3,pkg:github/erlang/otp@OTP-23.3.1,pkg:otp/ssl@10.3,pkg:github/erlang/otp@OTP-23.3.2,pkg:github/erlang/otp@OTP-23.3.3,pkg:github/erlang/otp@OTP-23.3.4,pkg:github/erlang/otp@OTP-23.3.4.1,pkg:otp/ssl@10.3.1,pkg:github/erlang/otp@OTP-23.3.4.2,pkg:github/erlang/otp@OTP-23.3.4.3,pkg:github/erlang/otp@OTP-23.3.4.4,pkg:otp/ssl@10.3.1.1,pkg:github/erlang/otp@OTP-23.3.4.5,pkg:github/erlang/otp@OTP-23.3.4.6,pkg:github/erlang/otp@OTP-23.3.4.7,pkg:github/erlang/otp@OTP-23.3.4.8,pkg:github/erlang/otp@OTP-23.3.4.9,pkg:github/erlang/otp@OTP-23.3.4.10,pkg:github/erlang/otp@OTP-23.3.4.11,pkg:github/erlang/otp@OTP-23.3.4.12,pkg:github/erlang/otp@OTP-23.3.4.13,pkg:github/erlang/otp@OTP-23.3.4.14,pkg:otp/ssl@10.3.1.2,pkg:github/erlang/otp@OTP-23.3.4.15,pkg:otp/ssl@10.3.1.3,pkg:github/erlang/otp@OTP-23.3.4.16,pkg:otp/ssl@10.3.1.4,pkg:github/erlang/otp@OTP-23.3.4.17,pkg:github/erlang/otp@OTP-23.3.4.18,pkg:github/erlang/otp@OTP-23.3.4.19,pkg:github/erlang/otp@OTP-23.3.4.20,pkg:otp/ssl@10.3.1.5' --vuln='F00' --status='under_investigation'\n",

~"vexctl add --in-place otp-23.openvex.json --product='pkg:github/erlang/otp@OTP-26.0,pkg:otp/erts@14.0,pkg:github/erlang/otp@OTP-26.0.1,pkg:otp/erts@14.0.1,pkg:github/erlang/otp@OTP-26.0.2,pkg:otp/erts@14.0.2,pkg:github/erlang/otp@OTP-26.1,pkg:github/erlang/otp@OTP-26.1.1,pkg:otp/erts@14.1,pkg:github/erlang/otp@OTP-26.1.2,pkg:otp/erts@14.1.1,pkg:github/erlang/otp@OTP-26.2,pkg:otp/erts@14.2,pkg:github/erlang/otp@OTP-26.2.1,pkg:otp/erts@14.2.1,pkg:github/erlang/otp@OTP-26.2.2,pkg:otp/erts@14.2.2,pkg:github/erlang/otp@OTP-26.2.3,pkg:otp/erts@14.2.3,pkg:github/erlang/otp@OTP-26.2.4,pkg:otp/erts@14.2.4,pkg:github/erlang/otp@OTP-26.2.5,pkg:otp/erts@14.2.5,pkg:github/erlang/otp@OTP-26.2.5.1,pkg:otp/erts@14.2.5.1,pkg:github/erlang/otp@OTP-26.2.5.2,pkg:otp/erts@14.2.5.2,pkg:github/erlang/otp@OTP-26.2.5.3,pkg:otp/erts@14.2.5.3,pkg:github/erlang/otp@OTP-26.2.5.4,pkg:github/erlang/otp@OTP-26.2.5.5,pkg:otp/erts@14.2.5.4,pkg:github/erlang/otp@OTP-26.2.5.6,pkg:otp/erts@14.2.5.5,pkg:github/erlang/otp@OTP-26.2.5.7,pkg:otp/erts@14.2.5.6,pkg:github/erlang/otp@OTP-26.2.5.8,pkg:otp/erts@14.2.5.7,pkg:github/erlang/otp@OTP-26.2.5.9,pkg:otp/erts@14.2.5.8,pkg:github/erlang/otp@OTP-26.2.5.10,pkg:github/erlang/otp@OTP-26.2.5.11,pkg:otp/erts@14.2.5.9,pkg:github/erlang/otp@OTP-26.2.5.12,pkg:github/erlang/otp@OTP-26.2.5.13,pkg:otp/erts@14.2.5.10,pkg:github/erlang/otp@OTP-26.2.5.14,pkg:github/erlang/otp@OTP-26.2.5.15,pkg:otp/erts@14.2.5.11' --vuln='CVE-2024-4444' --status='not_affected' --justification='vulnerable_code_not_present'\n",
Expand All @@ -3575,9 +3595,9 @@ test_openvex_branched_otp_tree() ->
%% idempotent: script runs once. if run again, no new vex statements are introduced,
%% because there was no change.
test_openvex_branched_otp_tree_idempotent() ->
{VexPath, Branch, VexStmts} = setup_openvex_test(fixup_openvex_branched_otp_tree_stmts()),
{_VexPath, Branch, VexStmts} = setup_openvex_test(fixup_openvex_branched_otp_tree_stmts()),
CVEs = fixup_openvex_branched_otp_tree(),
Result = calculate_statements_from_cves(VexStmts, CVEs, Branch, VexPath),
Result = calculate_statements_from_cves(VexStmts, CVEs, Branch),
true = Result == [],
ok.

Expand Down
Loading
Loading