96
96
% % VEX MACROS
97
97
% %
98
98
-define (VexPath , ~ " vex/" ).
99
+ -define (OpenVEXTablePath , " make/openvex.table" ).
99
100
-define (ErlangPURL , " pkg:github/erlang/otp" ).
100
101
101
102
-define (FOUND_VENDOR_VULNERABILITY_TITLE , " Vendor vulnerability found" ).
102
103
-define (FOUND_VENDOR_VULNERABILITY , lists :append (string :replace (? FOUND_VENDOR_VULNERABILITY_TITLE , " " , " +" , all ))).
103
104
105
+ -define (OTP_GH_URI , " https://raw.githubusercontent.com/" ++ ? GH_ACCOUNT ++ " /refs/heads/master/" ).
106
+
104
107
% % GH default options
105
108
-define (GH_ADVISORIES_OPTIONS , " state=published&direction=desc&per_page=100&sort=updated" ).
106
109
107
110
% % Advisories to download from last X years.
108
111
-define (GH_ADVISORIES_FROM_LAST_X_YEARS , 5 ).
109
112
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
+
110
116
% % Sets end point account to fetch information from GH
111
117
% % used by `gh` command-line tool.
112
118
% % change to your fork for testing, e.g., `kikofernandez/otp`
@@ -260,7 +266,8 @@ cli() ->
260
266
" osv-scan" =>
261
267
#{ help =>
262
268
"""
263
- Performs vulnerability scanning on vendor libraries
269
+ Performs vulnerability scanning on vendor libraries.
270
+ As a side effect,
264
271
265
272
Example:
266
273
@@ -295,10 +302,15 @@ cli() ->
295
302
#{ help =>
296
303
"""
297
304
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.
299
307
Creates PR for any non-present Github Advisory.
308
+
309
+ Example:
310
+ > .github/scripts/otp-compliance.es vex verify -p
311
+
300
312
""" ,
301
- arguments => [branch_option (), vex_path_option ()],
313
+ arguments => [create_pr ()],
302
314
handler => fun verify_openvex /1
303
315
},
304
316
@@ -480,6 +492,13 @@ vex_path_option() ->
480
492
help => " Path to folder containing openvex statements, e.g., `vex/`" ,
481
493
long => " -vex-path" }.
482
494
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
+
483
502
% %
484
503
% % Commands
485
504
% %
@@ -1497,7 +1516,7 @@ create_gh_issue(Version, Title, BodyText) ->
1497
1516
ok .
1498
1517
1499
1518
ignore_vex_cves (Branch , Vulns ) ->
1500
- OpenVex = get_otp_openvex_file (Branch ),
1519
+ OpenVex = download_otp_openvex_file (Branch ),
1501
1520
OpenVex1 = format_vex_statements (OpenVex ),
1502
1521
1503
1522
case OpenVex1 of
@@ -1544,33 +1563,54 @@ format_vex_statements(OpenVex) ->
1544
1563
Result ++ Acc
1545
1564
end , [], Stmts ).
1546
1565
1547
- get_otp_openvex_file (Branch ) ->
1548
- OpenVexPath = fetch_openvex_filename (Branch ),
1566
+ read_openvex_file (Branch ) ->
1567
+ _ = create_dir (? VexPath ),
1568
+ OpenVexPath = path_to_openvex_filename (Branch ),
1569
+ OpenVexStr = erlang :binary_to_list (OpenVexPath ),
1570
+ decode (OpenVexStr ).
1571
+
1572
+ - spec download_otp_openvex_file (Branch :: binary ()) -> Json :: map () | EmptyMap :: #{} | no_return ().
1573
+ download_otp_openvex_file (Branch ) ->
1574
+ _ = create_dir (? VexPath ),
1575
+ OpenVexPath = path_to_openvex_filename (Branch ),
1549
1576
OpenVexStr = erlang :binary_to_list (OpenVexPath ),
1550
- GithubURI = " https://raw.githubusercontent.com/ " ++ ? GH_ACCOUNT ++ " /refs/heads/master/ " ++ OpenVexStr ,
1577
+ GithubURI = get_gh_download_uri ( OpenVexStr ) ,
1551
1578
1552
1579
io :format (" Checking OpenVex statements in '~s ' from~n '~s '...~n " , [OpenVexPath , GithubURI ]),
1553
1580
1554
1581
ValidURI = " curl -I -Lj --silent " ++ GithubURI ++ " | head -n1 | cut -d' ' -f2" ,
1555
1582
case string :trim (os :cmd (ValidURI )) of
1556
1583
" 200" ->
1584
+ % % Overrides existing file.
1557
1585
io :format (" OpenVex file found.~n~n " ),
1558
1586
Command = " curl -LJ " ++ GithubURI ++ " --output " ++ OpenVexStr ,
1587
+ io :format (" Proceed to download:~n~s~n~n " , [Command ]),
1559
1588
os :cmd (Command , #{ exception_on_failure => true }),
1560
1589
decode (OpenVexStr );
1561
1590
E ->
1562
- io :format (" [~p ] No OpenVex file found.~n~n " , [E ]),
1591
+ io :format (" [~p ] No OpenVex statements found for file ' ~s ' .~n~n " , [E , OpenVexStr ]),
1563
1592
#{}
1564
1593
end .
1565
1594
1566
- fetch_openvex_filename (Branch ) ->
1595
+ - spec get_gh_download_uri (String :: list ()) -> String :: list ().
1596
+ get_gh_download_uri (File ) ->
1597
+ ? OTP_GH_URI ++ File .
1598
+
1599
+ - spec create_dir (DirName :: binary ()) -> ok | no_return ().
1600
+ create_dir (DirName ) ->
1601
+ case file :make_dir (DirName ) of
1602
+ Result when Result == ok ;
1603
+ Result == {error , eexist } ->
1604
+ io :format (" Directory ~s created successfully.~n " , [DirName ]);
1605
+ {error , Reason } ->
1606
+ fail (" Failed to create directory ~s : ~p~n " , [DirName , Reason ])
1607
+ end .
1608
+
1609
+ - spec path_to_openvex_filename (Branch :: binary ()) -> Path :: binary ().
1610
+ path_to_openvex_filename (Branch ) ->
1567
1611
_ = valid_scan_branches (Branch ),
1568
1612
Version = maint_to_otp_conversion (Branch ),
1569
1613
vex_path (Version ).
1570
- fetch_openvex_filename (Branch , VexPath ) ->
1571
- _ = valid_scan_branches (Branch ),
1572
- Version = maint_to_otp_conversion (Branch ),
1573
- vex_path (VexPath , Version ).
1574
1614
1575
1615
maint_to_otp_conversion (Branch ) ->
1576
1616
case Branch of
@@ -1588,6 +1628,7 @@ maint_to_otp_conversion(Branch) ->
1588
1628
OTP
1589
1629
end .
1590
1630
1631
+ - spec valid_scan_branches (Branch :: binary ()) -> ok | no_return ().
1591
1632
valid_scan_branches (Branch ) ->
1592
1633
case Branch of
1593
1634
~ " master" ->
@@ -2474,28 +2515,80 @@ run_openvex1(VexStmts, VexTableFile, Branch, VexPath) ->
2474
2515
Statements = calculate_statements (VexStmts , VexTableFile , Branch , VexPath ),
2475
2516
lists :foreach (fun (St ) -> io :format (" ~ts " , [St ]) end , Statements ).
2476
2517
2477
- verify_openvex (#{branch := Branch , vex_path := VexPath }) ->
2478
- UpdatedBranch = maint_to_otp_conversion (Branch ),
2479
- OpenVEX = read_openvex (VexPath , UpdatedBranch ),
2480
- Advisory = download_advisory_from_branch (UpdatedBranch ),
2481
- case verify_advisory_against_openvex (OpenVEX , Advisory ) of
2482
- [] ->
2483
- ok ;
2484
- MissingAdvisories when is_list (MissingAdvisories ) ->
2485
- create_advisory (MissingAdvisories )
2486
- end .
2487
-
2488
- read_openvex (VexPath , Branch ) ->
2489
- InitVex = fetch_openvex_filename (Branch , VexPath ),
2490
- case filelib :is_file (InitVex ) of
2491
- true -> % file exists
2492
- decode (InitVex );
2518
+ verify_openvex (#{create_pr := PR }) ->
2519
+ Branches = get_supported_branches (),
2520
+ io :format (" Sync ~p~n " , [Branches ]),
2521
+ _ = lists :foreach (
2522
+ fun (Branch ) ->
2523
+ case verify_openvex_advisories (Branch ) of
2524
+ [] ->
2525
+ io :format (" No new advisories nor OpenVEX statements created for '~s '." , [Branch ]);
2526
+ MissingAdvisories ->
2527
+ io :format (" Missing Advisories:~n~p~n~n " , [MissingAdvisories ]),
2528
+ case PR of
2529
+ false ->
2530
+ io :format (" To automatically update openvex.table and create a PR run:~n " ++
2531
+ " .github/scripts/otp-compliance.es vex verify -b ~s -p~n~n " , [Branch ]);
2532
+ true ->
2533
+ Advs = create_advisory (MissingAdvisories ),
2534
+ _ = update_openvex_otp_table (Branch , Advs ),
2535
+ BranchStr = erlang :binary_to_list (Branch ),
2536
+ _ = cmd (" .github/scripts/otp-compliance.es vex run -b " ++ BranchStr ++ " | bash" )
2537
+ end
2538
+ end
2539
+ end , Branches ),
2540
+ case PR of
2541
+ true ->
2542
+ cmd (" .github/scripts/create-openvex-pr.sh " ++ ? GH_ACCOUNT ++ " vex" );
2493
2543
false ->
2494
- throw ( file_not_found )
2544
+ ok
2495
2545
end .
2496
2546
2547
+ verify_openvex_advisories (Branch ) ->
2548
+ OpenVEX = read_openvex_file (Branch ),
2549
+ Advisory = download_advisory_from_branch (Branch ),
2550
+ verify_advisory_against_openvex (OpenVEX , Advisory ).
2551
+
2552
+ - spec get_supported_branches () -> [Branches :: binary ()].
2553
+ get_supported_branches () ->
2554
+ Branches = cmd (" .github/scripts/get-supported-branches.sh" ),
2555
+ BranchesBin = json :decode (erlang :list_to_binary (Branches )),
2556
+ io :format (" ~p~n~p~n " , [Branches , BranchesBin ]),
2557
+ lists :filtermap (fun (<<" maint-" , _ /binary >>= OTP ) -> {true , maint_to_otp_conversion (OTP )};
2558
+ (_ ) -> false
2559
+ end , BranchesBin ).
2560
+
2497
2561
create_advisory (Advisories ) ->
2498
- io :format (" Missing:~n~p~n~n " , [Advisories ]).
2562
+ lists :foldl (fun (Adv , Acc ) ->
2563
+ create_openvex_otp_entries (Adv ) ++ Acc
2564
+ end , [], Advisories ).
2565
+
2566
+ create_openvex_otp_entries (#{'CVE' := CVEId ,
2567
+ 'appName' := AppName ,
2568
+ 'affectedVersions' := AffectedVersions ,
2569
+ 'fixedVersions' := FixedVersions }) ->
2570
+ AppFixedVersions = lists :map (fun (Ver ) -> create_app_purl (AppName , Ver ) end , FixedVersions ),
2571
+ lists :map (fun (Affected ) ->
2572
+ Purl = create_app_purl (AppName , Affected ),
2573
+ create_openvex_app_entry (Purl , CVEId , AppFixedVersions )
2574
+ end , AffectedVersions ).
2575
+
2576
+ create_app_purl (AppName , Version ) when is_binary (AppName ), is_binary (Version ) ->
2577
+ <<" pkg:otp/" , AppName /binary , " @" , Version /binary >>.
2578
+
2579
+ create_openvex_app_entry (Purl , CVEId , FixedVersions ) ->
2580
+ #{Purl => CVEId ,
2581
+ ~ " status" =>
2582
+ #{ ~ " affected" => iolist_to_binary (io_lib :format (" Update to any of the following versions: ~s " , [FixedVersions ])),
2583
+ ~ " fixed" => FixedVersions }}.
2584
+
2585
+ update_openvex_otp_table (Branch , Advs ) ->
2586
+ Path = ? OpenVEXTablePath ,
2587
+ io :format (" OpenVEX Statements:~n~p~n~n " , [Advs ]),
2588
+ #{Branch := Statements }= Table = decode (Path ),
2589
+ UpdatedTable = Table #{Branch := Advs ++ Statements },
2590
+ io :format (" Update table:~n~p~n " , [UpdatedTable ]),
2591
+ file :write_file (Path , json :format (UpdatedTable )).
2499
2592
2500
2593
generate_gh_link (Part ) ->
2501
2594
" \" /repos/erlang/otp/security-advisories?" ++ Part ++ " \" " .
@@ -2886,7 +2979,8 @@ format_vexctl(VexPath, Versions, CVE, S) when S =:= ~"fixed";
2886
2979
[VexPath , Versions , CVE , S ]).
2887
2980
2888
2981
2889
- - spec fetch_otp_purl_versions (OTP :: binary (), FixedVersions :: [binary ()] ) -> OTPAppVersions :: binary ().
2982
+ - spec fetch_otp_purl_versions (OTP :: binary (), FixedVersions :: [binary ()] ) ->
2983
+ {AffectedPurls :: binary (), FixedPurls :: binary ()} | false .
2890
2984
fetch_otp_purl_versions (<<? ErlangPURL , _ /binary >>, _FixedVersions ) ->
2891
2985
% % ignore
2892
2986
false ;
0 commit comments