9696% % VEX MACROS
9797% %
9898-define (VexPath , ~ " vex/" ).
99+ -define (OpenVEXTablePath , " make/openvex.table" ).
99100-define (ErlangPURL , " pkg:github/erlang/otp" ).
100101
101102-define (FOUND_VENDOR_VULNERABILITY_TITLE , " Vendor vulnerability found" ).
102103-define (FOUND_VENDOR_VULNERABILITY , lists :append (string :replace (? FOUND_VENDOR_VULNERABILITY_TITLE , " " , " +" , all ))).
103104
105+ -define (OTP_GH_URI , " https://raw.githubusercontent.com/" ++ ? GH_ACCOUNT ++ " /refs/heads/master/" ).
106+
104107% % GH default options
105108-define (GH_ADVISORIES_OPTIONS , " state=published&direction=desc&per_page=100&sort=updated" ).
106109
107110% % Advisories to download from last X years.
108111-define (GH_ADVISORIES_FROM_LAST_X_YEARS , 5 ).
109112
113+ % % Defines path of script to create PRs for missing openvex/vulnerabilities
114+ -define (CREATE_OPENVEX_PR_SCRIPT_FILE , " .github/scripts/create-openvex-pr.sh" ).
115+
110116% % Sets end point account to fetch information from GH
111117% % used by `gh` command-line tool.
112118% % change to your fork for testing, e.g., `kikofernandez/otp`
@@ -260,7 +266,8 @@ cli() ->
260266 " osv-scan" =>
261267 #{ help =>
262268 """
263- Performs vulnerability scanning on vendor libraries
269+ Performs vulnerability scanning on vendor libraries.
270+ As a side effect,
264271
265272 Example:
266273
@@ -295,10 +302,15 @@ cli() ->
295302 #{ help =>
296303 """
297304 Download Github Advisories for erlang/otp.
298- Checks that those are present in OpenVEX statements.
305+ Download OpenVEX statement from erlang/otp for the selected branch.
306+ Checks that those Advisories are present in OpenVEX statements.
299307 Creates PR for any non-present Github Advisory.
308+
309+ Example:
310+ > .github/scripts/otp-compliance.es vex verify -p
311+
300312 """ ,
301- arguments => [branch_option (), vex_path_option ()],
313+ arguments => [create_pr ()],
302314 handler => fun verify_openvex /1
303315 },
304316
@@ -480,6 +492,13 @@ vex_path_option() ->
480492 help => " Path to folder containing openvex statements, e.g., `vex/`" ,
481493 long => " -vex-path" }.
482494
495+ create_pr () ->
496+ #{name => create_pr ,
497+ short => $p ,
498+ type => boolean ,
499+ default => false ,
500+ help => " Indicates if missing OpenVEX statements create and submit a PR" }.
501+
483502% %
484503% % Commands
485504% %
@@ -1490,7 +1509,7 @@ create_gh_issue(Version, Title, BodyText) ->
14901509 ok .
14911510
14921511ignore_vex_cves (Branch , Vulns ) ->
1493- OpenVex = get_otp_openvex_file (Branch ),
1512+ OpenVex = download_otp_openvex_file (Branch ),
14941513 OpenVex1 = format_vex_statements (OpenVex ),
14951514
14961515 case OpenVex1 of
@@ -1537,33 +1556,54 @@ format_vex_statements(OpenVex) ->
15371556 Result ++ Acc
15381557 end , [], Stmts ).
15391558
1540- get_otp_openvex_file (Branch ) ->
1541- OpenVexPath = fetch_openvex_filename (Branch ),
1559+ read_openvex_file (Branch ) ->
1560+ _ = create_dir (? VexPath ),
1561+ OpenVexPath = path_to_openvex_filename (Branch ),
1562+ OpenVexStr = erlang :binary_to_list (OpenVexPath ),
1563+ decode (OpenVexStr ).
1564+
1565+ - spec download_otp_openvex_file (Branch :: binary ()) -> Json :: map () | EmptyMap :: #{} | no_return ().
1566+ download_otp_openvex_file (Branch ) ->
1567+ _ = create_dir (? VexPath ),
1568+ OpenVexPath = path_to_openvex_filename (Branch ),
15421569 OpenVexStr = erlang :binary_to_list (OpenVexPath ),
1543- GithubURI = " https://raw.githubusercontent.com/ " ++ ? GH_ACCOUNT ++ " /refs/heads/master/ " ++ OpenVexStr ,
1570+ GithubURI = get_gh_download_uri ( OpenVexStr ) ,
15441571
15451572 io :format (" Checking OpenVex statements in '~s ' from~n '~s '...~n " , [OpenVexPath , GithubURI ]),
15461573
15471574 ValidURI = " curl -I -Lj --silent " ++ GithubURI ++ " | head -n1 | cut -d' ' -f2" ,
15481575 case string :trim (os :cmd (ValidURI )) of
15491576 " 200" ->
1577+ % % Overrides existing file.
15501578 io :format (" OpenVex file found.~n~n " ),
15511579 Command = " curl -LJ " ++ GithubURI ++ " --output " ++ OpenVexStr ,
1580+ io :format (" Proceed to download:~n~s~n~n " , [Command ]),
15521581 os :cmd (Command , #{ exception_on_failure => true }),
15531582 decode (OpenVexStr );
15541583 E ->
1555- io :format (" [~p ] No OpenVex file found.~n~n " , [E ]),
1584+ io :format (" [~p ] No OpenVex statements found for file ' ~s ' .~n~n " , [E , OpenVexStr ]),
15561585 #{}
15571586 end .
15581587
1559- fetch_openvex_filename (Branch ) ->
1588+ - spec get_gh_download_uri (String :: list ()) -> String :: list ().
1589+ get_gh_download_uri (File ) ->
1590+ ? OTP_GH_URI ++ File .
1591+
1592+ - spec create_dir (DirName :: binary ()) -> ok | no_return ().
1593+ create_dir (DirName ) ->
1594+ case file :make_dir (DirName ) of
1595+ Result when Result == ok ;
1596+ Result == {error , eexist } ->
1597+ io :format (" Directory ~s created successfully.~n " , [DirName ]);
1598+ {error , Reason } ->
1599+ fail (" Failed to create directory ~s : ~p~n " , [DirName , Reason ])
1600+ end .
1601+
1602+ - spec path_to_openvex_filename (Branch :: binary ()) -> Path :: binary ().
1603+ path_to_openvex_filename (Branch ) ->
15601604 _ = valid_scan_branches (Branch ),
15611605 Version = maint_to_otp_conversion (Branch ),
15621606 vex_path (Version ).
1563- fetch_openvex_filename (Branch , VexPath ) ->
1564- _ = valid_scan_branches (Branch ),
1565- Version = maint_to_otp_conversion (Branch ),
1566- vex_path (VexPath , Version ).
15671607
15681608maint_to_otp_conversion (Branch ) ->
15691609 case Branch of
@@ -1581,6 +1621,7 @@ maint_to_otp_conversion(Branch) ->
15811621 OTP
15821622 end .
15831623
1624+ - spec valid_scan_branches (Branch :: binary ()) -> ok | no_return ().
15841625valid_scan_branches (Branch ) ->
15851626 case Branch of
15861627 ~ " master" ->
@@ -2467,28 +2508,80 @@ run_openvex1(VexStmts, VexTableFile, Branch, VexPath) ->
24672508 Statements = calculate_statements (VexStmts , VexTableFile , Branch , VexPath ),
24682509 lists :foreach (fun (St ) -> io :format (" ~ts " , [St ]) end , Statements ).
24692510
2470- verify_openvex (#{branch := Branch , vex_path := VexPath }) ->
2471- UpdatedBranch = maint_to_otp_conversion (Branch ),
2472- OpenVEX = read_openvex (VexPath , UpdatedBranch ),
2473- Advisory = download_advisory_from_branch (UpdatedBranch ),
2474- case verify_advisory_against_openvex (OpenVEX , Advisory ) of
2475- [] ->
2476- ok ;
2477- MissingAdvisories when is_list (MissingAdvisories ) ->
2478- create_advisory (MissingAdvisories )
2479- end .
2480-
2481- read_openvex (VexPath , Branch ) ->
2482- InitVex = fetch_openvex_filename (Branch , VexPath ),
2483- case filelib :is_file (InitVex ) of
2484- true -> % file exists
2485- decode (InitVex );
2511+ verify_openvex (#{create_pr := PR }) ->
2512+ Branches = get_supported_branches (),
2513+ io :format (" Sync ~p~n " , [Branches ]),
2514+ _ = lists :foreach (
2515+ fun (Branch ) ->
2516+ case verify_openvex_advisories (Branch ) of
2517+ [] ->
2518+ io :format (" No new advisories nor OpenVEX statements created for '~s '." , [Branch ]);
2519+ MissingAdvisories ->
2520+ io :format (" Missing Advisories:~n~p~n~n " , [MissingAdvisories ]),
2521+ case PR of
2522+ false ->
2523+ io :format (" To automatically update openvex.table and create a PR run:~n " ++
2524+ " .github/scripts/otp-compliance.es vex verify -b ~s -p~n~n " , [Branch ]);
2525+ true ->
2526+ Advs = create_advisory (MissingAdvisories ),
2527+ _ = update_openvex_otp_table (Branch , Advs ),
2528+ BranchStr = erlang :binary_to_list (Branch ),
2529+ _ = cmd (" .github/scripts/otp-compliance.es vex run -b " ++ BranchStr ++ " | bash" )
2530+ end
2531+ end
2532+ end , Branches ),
2533+ case PR of
2534+ true ->
2535+ cmd (" .github/scripts/create-openvex-pr.sh " ++ ? GH_ACCOUNT ++ " vex" );
24862536 false ->
2487- throw ( file_not_found )
2537+ ok
24882538 end .
24892539
2540+ verify_openvex_advisories (Branch ) ->
2541+ OpenVEX = read_openvex_file (Branch ),
2542+ Advisory = download_advisory_from_branch (Branch ),
2543+ verify_advisory_against_openvex (OpenVEX , Advisory ).
2544+
2545+ - spec get_supported_branches () -> [Branches :: binary ()].
2546+ get_supported_branches () ->
2547+ Branches = cmd (" .github/scripts/get-supported-branches.sh" ),
2548+ BranchesBin = json :decode (erlang :list_to_binary (Branches )),
2549+ io :format (" ~p~n~p~n " , [Branches , BranchesBin ]),
2550+ lists :filtermap (fun (<<" maint-" , _ /binary >>= OTP ) -> {true , maint_to_otp_conversion (OTP )};
2551+ (_ ) -> false
2552+ end , BranchesBin ).
2553+
24902554create_advisory (Advisories ) ->
2491- io :format (" Missing:~n~p~n~n " , [Advisories ]).
2555+ lists :foldl (fun (Adv , Acc ) ->
2556+ create_openvex_otp_entries (Adv ) ++ Acc
2557+ end , [], Advisories ).
2558+
2559+ create_openvex_otp_entries (#{'CVE' := CVEId ,
2560+ 'appName' := AppName ,
2561+ 'affectedVersions' := AffectedVersions ,
2562+ 'fixedVersions' := FixedVersions }) ->
2563+ AppFixedVersions = lists :map (fun (Ver ) -> create_app_purl (AppName , Ver ) end , FixedVersions ),
2564+ lists :map (fun (Affected ) ->
2565+ Purl = create_app_purl (AppName , Affected ),
2566+ create_openvex_app_entry (Purl , CVEId , AppFixedVersions )
2567+ end , AffectedVersions ).
2568+
2569+ create_app_purl (AppName , Version ) when is_binary (AppName ), is_binary (Version ) ->
2570+ <<" pkg:otp/" , AppName /binary , " @" , Version /binary >>.
2571+
2572+ create_openvex_app_entry (Purl , CVEId , FixedVersions ) ->
2573+ #{Purl => CVEId ,
2574+ ~ " status" =>
2575+ #{ ~ " affected" => iolist_to_binary (io_lib :format (" Update to any of the following versions: ~s " , [FixedVersions ])),
2576+ ~ " fixed" => FixedVersions }}.
2577+
2578+ update_openvex_otp_table (Branch , Advs ) ->
2579+ Path = ? OpenVEXTablePath ,
2580+ io :format (" OpenVEX Statements:~n~p~n~n " , [Advs ]),
2581+ #{Branch := Statements }= Table = decode (Path ),
2582+ UpdatedTable = Table #{Branch := Advs ++ Statements },
2583+ io :format (" Update table:~n~p~n " , [UpdatedTable ]),
2584+ file :write_file (Path , json :format (UpdatedTable )).
24922585
24932586generate_gh_link (Part ) ->
24942587 " \" /repos/erlang/otp/security-advisories?" ++ Part ++ " \" " .
@@ -2879,7 +2972,8 @@ format_vexctl(VexPath, Versions, CVE, S) when S =:= ~"fixed";
28792972 [VexPath , Versions , CVE , S ]).
28802973
28812974
2882- - spec fetch_otp_purl_versions (OTP :: binary (), FixedVersions :: [binary ()] ) -> OTPAppVersions :: binary ().
2975+ - spec fetch_otp_purl_versions (OTP :: binary (), FixedVersions :: [binary ()] ) ->
2976+ {AffectedPurls :: binary (), FixedPurls :: binary ()} | false .
28832977fetch_otp_purl_versions (<<? ErlangPURL , _ /binary >>, _FixedVersions ) ->
28842978 % % ignore
28852979 false ;
0 commit comments