Skip to content

Commit 6a70794

Browse files
committed
Address issue #612
New `—cert-file`, `—key-file` and `—ca-file` global flags added that allow customer certificates to be loaded and used.
1 parent 7291462 commit 6a70794

16 files changed

+326
-26
lines changed

cmd/build_results.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"github.com/daveshanley/vacuum/model"
77
"github.com/daveshanley/vacuum/motor"
88
"github.com/daveshanley/vacuum/rulesets"
9+
"github.com/daveshanley/vacuum/utils"
910
"github.com/pterm/pterm"
11+
"net/http"
1012
"os"
1113
"strings"
1214
"time"
@@ -20,8 +22,9 @@ func BuildResults(
2022
customFunctions map[string]model.RuleFunction,
2123
base string,
2224
remote bool,
23-
timeout time.Duration) (*model.RuleResultSet, *motor.RuleSetExecutionResult, error) {
24-
return BuildResultsWithDocCheckSkip(silent, hardMode, rulesetFlag, specBytes, customFunctions, base, remote, false, timeout)
25+
timeout time.Duration,
26+
httpClientConfig utils.HTTPClientConfig) (*model.RuleResultSet, *motor.RuleSetExecutionResult, error) {
27+
return BuildResultsWithDocCheckSkip(silent, hardMode, rulesetFlag, specBytes, customFunctions, base, remote, false, timeout, httpClientConfig)
2528
}
2629

2730
func BuildResultsWithDocCheckSkip(
@@ -33,7 +36,8 @@ func BuildResultsWithDocCheckSkip(
3336
base string,
3437
remote bool,
3538
skipCheck bool,
36-
timeout time.Duration) (*model.RuleResultSet, *motor.RuleSetExecutionResult, error) {
39+
timeout time.Duration,
40+
httpClientConfig utils.HTTPClientConfig) (*model.RuleResultSet, *motor.RuleSetExecutionResult, error) {
3741

3842
// read spec and parse
3943
defaultRuleSets := rulesets.BuildDefaultRuleSets()
@@ -62,24 +66,35 @@ func BuildResultsWithDocCheckSkip(
6266
// if ruleset has been supplied, lets make sure it exists, then load it in
6367
// and see if it's valid. If so - let's go!
6468
if rulesetFlag != "" {
69+
70+
// Create HTTP client for remote ruleset downloads if needed
71+
var httpClient *http.Client
72+
if utils.ShouldUseCustomHTTPClient(httpClientConfig) {
73+
var clientErr error
74+
httpClient, clientErr = utils.CreateCustomHTTPClient(httpClientConfig)
75+
if clientErr != nil {
76+
return nil, nil, fmt.Errorf("failed to create custom HTTP client: %w", clientErr)
77+
}
78+
}
6579

6680
if strings.HasPrefix(rulesetFlag, "http") {
6781
// Handle remote ruleset URL
6882
if !remote {
6983
return nil, nil, fmt.Errorf("remote ruleset specified but remote flag is disabled (use --remote=true or -u=true)")
7084
}
71-
downloadedRS, rsErr := rulesets.DownloadRemoteRuleSet(context.Background(), rulesetFlag)
85+
86+
downloadedRS, rsErr := rulesets.DownloadRemoteRuleSet(context.Background(), rulesetFlag, httpClient)
7287
if rsErr != nil {
7388
return nil, nil, rsErr
7489
}
75-
selectedRS = defaultRuleSets.GenerateRuleSetFromSuppliedRuleSet(downloadedRS)
90+
selectedRS = defaultRuleSets.GenerateRuleSetFromSuppliedRuleSetWithHTTPClient(downloadedRS, httpClient)
7691
} else {
7792
// Handle local ruleset file
7893
rsBytes, rsErr := os.ReadFile(rulesetFlag)
7994
if rsErr != nil {
8095
return nil, nil, rsErr
8196
}
82-
selectedRS, rsErr = BuildRuleSetFromUserSuppliedSet(rsBytes, defaultRuleSets)
97+
selectedRS, rsErr = BuildRuleSetFromUserSuppliedSetWithHTTPClient(rsBytes, defaultRuleSets, httpClient)
8398
if rsErr != nil {
8499
return nil, nil, rsErr
85100
}
@@ -96,6 +111,7 @@ func BuildResultsWithDocCheckSkip(
96111
SkipDocumentCheck: skipCheck,
97112
AllowLookup: remote,
98113
Timeout: timeout,
114+
HTTPClientConfig: httpClientConfig,
99115
})
100116

101117
resultSet := model.NewRuleResultSet(ruleset.Results)

cmd/build_results_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package cmd
22

33
import (
4+
"github.com/daveshanley/vacuum/utils"
45
"github.com/stretchr/testify/assert"
56
"testing"
67
)
78

89
func TestBuildResults(t *testing.T) {
9-
_, _, err := BuildResults(false, false, "nuggets", nil, nil, "", true, 5)
10+
_, _, err := BuildResults(false, false, "nuggets", nil, nil, "", true, 5, utils.HTTPClientConfig{})
1011
assert.Error(t, err)
1112
}
1213

1314
func TestBuildResults_SkipCheck(t *testing.T) {
14-
_, _, err := BuildResultsWithDocCheckSkip(false, false, "nuggets", nil, nil, "", true, true, 5)
15+
_, _, err := BuildResultsWithDocCheckSkip(false, false, "nuggets", nil, nil, "", true, true, 5, utils.HTTPClientConfig{})
1516
assert.Error(t, err)
1617
}

cmd/dashboard.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/daveshanley/vacuum/cui"
99
"github.com/daveshanley/vacuum/model"
1010
"github.com/daveshanley/vacuum/motor"
11+
"github.com/daveshanley/vacuum/utils"
1112
vacuum_report "github.com/daveshanley/vacuum/vacuum-report"
1213
"github.com/pb33f/libopenapi/datamodel"
1314
"github.com/pb33f/libopenapi/index"
@@ -67,8 +68,19 @@ func GetDashboardCommand() *cobra.Command {
6768
customFunctions, _ := LoadCustomFunctions(functionsFlag, silent)
6869

6970
rulesetFlag, _ := cmd.Flags().GetString("ruleset")
71+
72+
// Certificate/TLS configuration
73+
certFile, _ := cmd.Flags().GetString("cert-file")
74+
keyFile, _ := cmd.Flags().GetString("key-file")
75+
caFile, _ := cmd.Flags().GetString("ca-file")
76+
insecure, _ := cmd.Flags().GetBool("insecure")
7077
resultSet, ruleset, err = BuildResultsWithDocCheckSkip(false, hardModeFlag, rulesetFlag, specBytes, customFunctions,
71-
baseFlag, remoteFlag, skipCheckFlag, time.Duration(timeoutFlag)*time.Second)
78+
baseFlag, remoteFlag, skipCheckFlag, time.Duration(timeoutFlag)*time.Second, utils.HTTPClientConfig{
79+
CertFile: certFile,
80+
KeyFile: keyFile,
81+
CAFile: caFile,
82+
Insecure: insecure,
83+
})
7284
if err != nil {
7385
pterm.Error.Printf("Failed to render dashboard: %v\n\n", err)
7486
return err

cmd/html_report.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/daveshanley/vacuum/model/reports"
1111
"github.com/daveshanley/vacuum/motor"
1212
"github.com/daveshanley/vacuum/statistics"
13+
"github.com/daveshanley/vacuum/utils"
1314
vacuum_report "github.com/daveshanley/vacuum/vacuum-report"
1415
"github.com/pb33f/libopenapi/datamodel"
1516
"github.com/pb33f/libopenapi/index"
@@ -96,8 +97,20 @@ func GetHTMLReportCommand() *cobra.Command {
9697
customFunctions, _ := LoadCustomFunctions(functionsFlag, silent)
9798

9899
rulesetFlag, _ := cmd.Flags().GetString("ruleset")
100+
101+
// Certificate/TLS configuration
102+
certFile, _ := cmd.Flags().GetString("cert-file")
103+
keyFile, _ := cmd.Flags().GetString("key-file")
104+
caFile, _ := cmd.Flags().GetString("ca-file")
105+
insecure, _ := cmd.Flags().GetBool("insecure")
106+
99107
resultSet, ruleset, err = BuildResultsWithDocCheckSkip(false, hardModeFlag, rulesetFlag, specBytes, customFunctions,
100-
baseFlag, remoteFlag, skipCheckFlag, time.Duration(timeoutFlag)*time.Second)
108+
baseFlag, remoteFlag, skipCheckFlag, time.Duration(timeoutFlag)*time.Second, utils.HTTPClientConfig{
109+
CertFile: certFile,
110+
KeyFile: keyFile,
111+
CAFile: caFile,
112+
Insecure: insecure,
113+
})
101114
if err != nil {
102115
pterm.Error.Printf("Failed to generate report: %v\n\n", err)
103116
return err

cmd/language_server.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/spf13/cobra"
1313
"io"
1414
"log/slog"
15+
"net/http"
1516
)
1617

1718
func GetLanguageServerCommand() *cobra.Command {
@@ -60,8 +61,31 @@ IDE and start linting your OpenAPI documents in real-time.`,
6061

6162
if rulesetFlag != "" {
6263
remoteFlag, _ := cmd.Flags().GetBool("remote")
64+
65+
// Certificate/TLS configuration for language server
66+
certFile, _ := cmd.Flags().GetString("cert-file")
67+
keyFile, _ := cmd.Flags().GetString("key-file")
68+
caFile, _ := cmd.Flags().GetString("ca-file")
69+
insecure, _ := cmd.Flags().GetBool("insecure")
70+
71+
// Create HTTP client for remote ruleset downloads if needed
72+
var httpClient *http.Client
73+
httpClientConfig := utils.HTTPClientConfig{
74+
CertFile: certFile,
75+
KeyFile: keyFile,
76+
CAFile: caFile,
77+
Insecure: insecure,
78+
}
79+
if utils.ShouldUseCustomHTTPClient(httpClientConfig) {
80+
var clientErr error
81+
httpClient, clientErr = utils.CreateCustomHTTPClient(httpClientConfig)
82+
if clientErr != nil {
83+
return clientErr
84+
}
85+
}
86+
6387
var rsErr error
64-
selectedRS, rsErr = BuildRuleSetFromUserSuppliedLocation(rulesetFlag, defaultRuleSets, remoteFlag)
88+
selectedRS, rsErr = BuildRuleSetFromUserSuppliedLocation(rulesetFlag, defaultRuleSets, remoteFlag, httpClient)
6589
if rsErr != nil {
6690
return rsErr
6791
}

cmd/lint.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/daveshanley/vacuum/statistics"
1111
"gopkg.in/yaml.v3"
1212
"log/slog"
13+
"net/http"
1314
"os"
1415
"path/filepath"
1516
"strconv"
@@ -73,6 +74,12 @@ func GetLintCommand() *cobra.Command {
7374
// https://github.com/daveshanley/vacuum/issues/636
7475
showRules, _ := cmd.Flags().GetBool("show-rules")
7576

77+
// Certificate/TLS configuration
78+
certFile, _ := cmd.Flags().GetString("cert-file")
79+
keyFile, _ := cmd.Flags().GetString("key-file")
80+
caFile, _ := cmd.Flags().GetString("ca-file")
81+
insecure, _ := cmd.Flags().GetBool("insecure")
82+
7683
// disable color and styling, for CI/CD use.
7784
// https://github.com/daveshanley/vacuum/issues/234
7885
if noStyleFlag || pipelineOutput {
@@ -180,8 +187,25 @@ func GetLintCommand() *cobra.Command {
180187
// and see if it's valid. If so - let's go!
181188
if rulesetFlag != "" {
182189

190+
// Create HTTP client for remote ruleset downloads if needed
191+
var httpClient *http.Client
192+
httpClientConfig := utils.HTTPClientConfig{
193+
CertFile: certFile,
194+
KeyFile: keyFile,
195+
CAFile: caFile,
196+
Insecure: insecure,
197+
}
198+
if utils.ShouldUseCustomHTTPClient(httpClientConfig) {
199+
var clientErr error
200+
httpClient, clientErr = utils.CreateCustomHTTPClient(httpClientConfig)
201+
if clientErr != nil {
202+
pterm.Error.Printf("Failed to create custom HTTP client: %s\n", clientErr.Error())
203+
return clientErr
204+
}
205+
}
206+
183207
var rsErr error
184-
selectedRS, rsErr = BuildRuleSetFromUserSuppliedLocation(rulesetFlag, defaultRuleSets, remoteFlag)
208+
selectedRS, rsErr = BuildRuleSetFromUserSuppliedLocation(rulesetFlag, defaultRuleSets, remoteFlag, httpClient)
185209
if rsErr != nil {
186210
pterm.Error.Printf("Unable to load ruleset '%s': %s\n", rulesetFlag, rsErr.Error())
187211
pterm.Println()
@@ -273,6 +297,12 @@ func GetLintCommand() *cobra.Command {
273297
ExtensionRefs: extensionRefsFlag,
274298
PipelineOutput: pipelineOutput,
275299
ShowRules: showRules,
300+
HTTPClientConfig: utils.HTTPClientConfig{
301+
CertFile: certFile,
302+
KeyFile: keyFile,
303+
CAFile: caFile,
304+
Insecure: insecure,
305+
},
276306
}
277307
st, fs, fp, err := lintFile(lfr)
278308

@@ -413,6 +443,7 @@ func lintFile(req utils.LintFileRequest) (*reports.ReportStatistics, int64, int,
413443
IgnoreCircularArrayRef: req.IgnoreArrayCircleRef,
414444
IgnoreCircularPolymorphicRef: req.IgnorePolymorphCircleRef,
415445
ExtractReferencesFromExtensions: req.ExtensionRefs,
446+
HTTPClientConfig: req.HTTPClientConfig,
416447
})
417448

418449
result.Results = filterIgnoredResults(result.Results, req.IgnoredResults)

cmd/root.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ func GetRootCommand() *cobra.Command {
6565
rootCmd.PersistentFlags().IntP("timeout", "g", 5, "Rule timeout in seconds, default is 5 seconds")
6666
rootCmd.PersistentFlags().BoolP("hard-mode", "z", false, "Enable all the built-in rules, even the OWASP ones. This is the level to beat!")
6767
rootCmd.PersistentFlags().BoolP("ext-refs", "", false, "Turn on $ref lookups and resolving for extensions (x-) objects")
68+
rootCmd.PersistentFlags().String("cert-file", "", "Path to client certificate file for HTTPS requests")
69+
rootCmd.PersistentFlags().String("key-file", "", "Path to client private key file for HTTPS requests")
70+
rootCmd.PersistentFlags().String("ca-file", "", "Path to CA certificate file for HTTPS requests")
71+
rootCmd.PersistentFlags().Bool("insecure", false, "Skip TLS certificate verification (insecure)")
6872

6973
if regErr := rootCmd.RegisterFlagCompletionFunc("functions", cobra.FixedCompletions(
7074
[]string{"so"}, cobra.ShellCompDirectiveFilterFileExt,
@@ -79,6 +83,24 @@ func GetRootCommand() *cobra.Command {
7983
if regErr := rootCmd.RegisterFlagCompletionFunc("timeout", cobra.NoFileCompletions); regErr != nil {
8084
panic(regErr)
8185
}
86+
if regErr := rootCmd.RegisterFlagCompletionFunc("cert-file", cobra.FixedCompletions(
87+
[]string{"crt", "pem", "cert"}, cobra.ShellCompDirectiveFilterFileExt,
88+
)); regErr != nil {
89+
panic(regErr)
90+
}
91+
if regErr := rootCmd.RegisterFlagCompletionFunc("key-file", cobra.FixedCompletions(
92+
[]string{"key", "pem"}, cobra.ShellCompDirectiveFilterFileExt,
93+
)); regErr != nil {
94+
panic(regErr)
95+
}
96+
if regErr := rootCmd.RegisterFlagCompletionFunc("ca-file", cobra.FixedCompletions(
97+
[]string{"crt", "pem", "cert"}, cobra.ShellCompDirectiveFilterFileExt,
98+
)); regErr != nil {
99+
panic(regErr)
100+
}
101+
if regErr := rootCmd.RegisterFlagCompletionFunc("insecure", cobra.NoFileCompletions); regErr != nil {
102+
panic(regErr)
103+
}
82104

83105
rootCmd.AddCommand(GetLintCommand())
84106
rootCmd.AddCommand(GetVacuumReportCommand())

cmd/shared_functions.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/dustin/go-humanize"
1313
"github.com/pb33f/libopenapi/index"
1414
"github.com/pterm/pterm"
15+
"net/http"
1516
"os"
1617
"strings"
1718
"time"
@@ -21,6 +22,12 @@ import (
2122
// BuildRuleSetFromUserSuppliedSet creates a ready to run ruleset, augmented or provided by a user
2223
// configured ruleset. This ruleset could be lifted directly from a Spectral configuration.
2324
func BuildRuleSetFromUserSuppliedSet(rsBytes []byte, rs rulesets.RuleSets) (*rulesets.RuleSet, error) {
25+
return BuildRuleSetFromUserSuppliedSetWithHTTPClient(rsBytes, rs, nil)
26+
}
27+
28+
// BuildRuleSetFromUserSuppliedSetWithHTTPClient creates a ready to run ruleset, augmented or provided by a user
29+
// configured ruleset with HTTP client support for certificate authentication.
30+
func BuildRuleSetFromUserSuppliedSetWithHTTPClient(rsBytes []byte, rs rulesets.RuleSets, httpClient *http.Client) (*rulesets.RuleSet, error) {
2431

2532
// load in our user supplied ruleset and try to validate it.
2633
userRS, userErr := rulesets.CreateRuleSetFromData(rsBytes)
@@ -30,28 +37,28 @@ func BuildRuleSetFromUserSuppliedSet(rsBytes []byte, rs rulesets.RuleSets) (*rul
3037
return nil, userErr
3138

3239
}
33-
return rs.GenerateRuleSetFromSuppliedRuleSet(userRS), nil
40+
return rs.GenerateRuleSetFromSuppliedRuleSetWithHTTPClient(userRS, httpClient), nil
3441
}
3542

3643
// BuildRuleSetFromUserSuppliedLocation creates a ready to run ruleset from a location (file path or URL)
37-
func BuildRuleSetFromUserSuppliedLocation(rulesetFlag string, rs rulesets.RuleSets, remote bool) (*rulesets.RuleSet, error) {
44+
func BuildRuleSetFromUserSuppliedLocation(rulesetFlag string, rs rulesets.RuleSets, remote bool, httpClient *http.Client) (*rulesets.RuleSet, error) {
3845
if strings.HasPrefix(rulesetFlag, "http") {
3946
// Handle remote ruleset URL directly
4047
if !remote {
4148
return nil, fmt.Errorf("remote ruleset specified but remote flag is disabled (use --remote=true or -u=true)")
4249
}
43-
downloadedRS, rsErr := rulesets.DownloadRemoteRuleSet(context.Background(), rulesetFlag)
50+
downloadedRS, rsErr := rulesets.DownloadRemoteRuleSet(context.Background(), rulesetFlag, httpClient)
4451
if rsErr != nil {
4552
return nil, rsErr
4653
}
47-
return rs.GenerateRuleSetFromSuppliedRuleSet(downloadedRS), nil
54+
return rs.GenerateRuleSetFromSuppliedRuleSetWithHTTPClient(downloadedRS, httpClient), nil
4855
} else {
4956
// Handle local ruleset file
5057
rsBytes, rsErr := os.ReadFile(rulesetFlag)
5158
if rsErr != nil {
5259
return nil, rsErr
5360
}
54-
return BuildRuleSetFromUserSuppliedSet(rsBytes, rs)
61+
return BuildRuleSetFromUserSuppliedSetWithHTTPClient(rsBytes, rs, httpClient)
5562
}
5663
}
5764

0 commit comments

Comments
 (0)