From 7b9aae1d8bc87d6520576f4b3ed0d851a19e59ff Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 19 Jan 2020 15:18:00 -0500 Subject: [PATCH 1/5] lint: split base.go into more files --- lint/base.go | 66 --------------------------------------- lint/base_test.go | 22 ------------- lint/registration.go | 16 ++++++++++ lint/registration_test.go | 39 +++++++++++++++++++++++ lint/source.go | 66 +++++++++++++++++++++++++++++++++++++++ lints/registration.go | 30 ++++++++++++++++++ 6 files changed, 151 insertions(+), 88 deletions(-) create mode 100644 lint/registration.go create mode 100644 lint/registration_test.go create mode 100644 lint/source.go create mode 100644 lints/registration.go diff --git a/lint/base.go b/lint/base.go index 06a78313e..4335638cf 100644 --- a/lint/base.go +++ b/lint/base.go @@ -21,12 +21,6 @@ import ( "github.com/zmap/zlint/util" ) -var ( - // Lints is a map of all known lints by name. Add a Lint to the map by calling - // RegisterLint. - Lints = make(map[string]*Lint) -) - // LintInterface is implemented by each Lint. type LintInterface interface { // Initialize runs once per-lint. It is called during RegisterLint(). @@ -43,57 +37,6 @@ type LintInterface interface { Execute(c *x509.Certificate) *LintResult } -// An Enum to programmatically represent the source of a lint -type LintSource int - -// NOTE(@cpu): If you are adding a new LintSource make sure you have considered -// updating the Directory() function. -const ( - UnknownLintSource LintSource = iota - CABFBaselineRequirements - RFC5280 - RFC5480 - RFC5891 - ZLint - AWSLabs - EtsiEsi // ETSI - Electronic Signatures and Infrastructures (ESI) - CABFEVGuidelines - AppleCTPolicy // https://support.apple.com/en-us/HT205280 - MozillaRootStorePolicy // https://github.com/mozilla/pkipolicy -) - -// LintSources contains a list of the valid lint sources we expect to be used -// by ZLint lints. -var LintSources = []LintSource{ - CABFBaselineRequirements, - CABFEVGuidelines, - RFC5280, - RFC5480, - RFC5891, - AppleCTPolicy, - EtsiEsi, - ZLint, - AWSLabs, -} - -// Directory returns the directory name in `lints/` for the LintSource. -func (l LintSource) Directory() string { - switch l { - case CABFBaselineRequirements: - return "cabf_br" - case CABFEVGuidelines: - return "cabf_ev" - case RFC5280, RFC5480, RFC5891: - return "rfc" - case AppleCTPolicy: - return "apple" - case EtsiEsi: - return "etsi" - default: - return "community" - } -} - // A Lint struct represents a single lint, e.g. // "e_basic_constraints_not_critical". It contains an implementation of LintInterface. type Lint struct { @@ -151,12 +94,3 @@ func (l *Lint) Execute(cert *x509.Certificate) *LintResult { res := l.Lint.Execute(cert) return res } - -// RegisterLint must be called once for each lint to be excuted. Duplicate lint -// names are squashed. Normally, RegisterLint is called during init(). -func RegisterLint(l *Lint) { - if err := l.Lint.Initialize(); err != nil { - panic("could not initialize lint: " + l.Name + ": " + err.Error()) - } - Lints[l.Name] = l -} diff --git a/lint/base_test.go b/lint/base_test.go index c1de2c802..20fe7221c 100644 --- a/lint/base_test.go +++ b/lint/base_test.go @@ -21,28 +21,6 @@ import ( "github.com/zmap/zcrypto/x509" ) -func TestAllLintsHaveNameDescriptionSource(t *testing.T) { - for name, lint := range Lints { - if lint.Name == "" { - t.Errorf("lint %s has empty name", name) - } - if lint.Description == "" { - t.Errorf("lint %s has empty description", name) - } - if lint.Citation == "" { - t.Errorf("lint %s has empty citation", name) - } - } -} - -func TestAllLintsHaveSource(t *testing.T) { - for name, lint := range Lints { - if lint.Source == UnknownLintSource { - t.Errorf("lint %s has unknown source", name) - } - } -} - func TestLintCheckEffective(t *testing.T) { c := &x509.Certificate{ NotBefore: time.Now(), diff --git a/lint/registration.go b/lint/registration.go new file mode 100644 index 000000000..24125174c --- /dev/null +++ b/lint/registration.go @@ -0,0 +1,16 @@ +package lint + +var ( + // Lints is a map of all known lints by name. Add a Lint to the map by calling + // RegisterLint. + Lints = make(map[string]*Lint) +) + +// RegisterLint must be called once for each lint to be excuted. Duplicate lint +// names are squashed. Normally, RegisterLint is called during init(). +func RegisterLint(l *Lint) { + if err := l.Lint.Initialize(); err != nil { + panic("could not initialize lint: " + l.Name + ": " + err.Error()) + } + Lints[l.Name] = l +} diff --git a/lint/registration_test.go b/lint/registration_test.go new file mode 100644 index 000000000..a066e78ff --- /dev/null +++ b/lint/registration_test.go @@ -0,0 +1,39 @@ +package lint + +/* + * ZLint Copyright 2020 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import "testing" + +func TestAllLintsHaveNameDescriptionSource(t *testing.T) { + for name, lint := range Lints { + if lint.Name == "" { + t.Errorf("lint %s has empty name", name) + } + if lint.Description == "" { + t.Errorf("lint %s has empty description", name) + } + if lint.Citation == "" { + t.Errorf("lint %s has empty citation", name) + } + } +} + +func TestAllLintsHaveSource(t *testing.T) { + for name, lint := range Lints { + if lint.Source == UnknownLintSource { + t.Errorf("lint %s has unknown source", name) + } + } +} diff --git a/lint/source.go b/lint/source.go new file mode 100644 index 000000000..fccb17d8b --- /dev/null +++ b/lint/source.go @@ -0,0 +1,66 @@ +package lint + +/* + * ZLint Copyright 2020 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +// An Enum to programmatically represent the source of a lint +type LintSource int + +// NOTE(@cpu): If you are adding a new LintSource make sure you have considered +// updating the Directory() function. +const ( + UnknownLintSource LintSource = iota + CABFBaselineRequirements + RFC5280 + RFC5480 + RFC5891 + ZLint + AWSLabs + EtsiEsi // ETSI - Electronic Signatures and Infrastructures (ESI) + CABFEVGuidelines + AppleCTPolicy // https://support.apple.com/en-us/HT205280 + MozillaRootStorePolicy // https://github.com/mozilla/pkipolicy +) + +// LintSources contains a list of the valid lint sources we expect to be used +// by ZLint lints. +var LintSources = []LintSource{ + CABFBaselineRequirements, + CABFEVGuidelines, + RFC5280, + RFC5480, + RFC5891, + AppleCTPolicy, + EtsiEsi, + ZLint, + AWSLabs, +} + +// Directory returns the directory name in `lints/` for the LintSource. +func (l LintSource) Directory() string { + switch l { + case CABFBaselineRequirements: + return "cabf_br" + case CABFEVGuidelines: + return "cabf_ev" + case RFC5280, RFC5480, RFC5891: + return "rfc" + case AppleCTPolicy: + return "apple" + case EtsiEsi: + return "etsi" + default: + return "community" + } +} diff --git a/lints/registration.go b/lints/registration.go new file mode 100644 index 000000000..a6e054672 --- /dev/null +++ b/lints/registration.go @@ -0,0 +1,30 @@ +package lint + +/* + * ZLint Copyright 2020 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +var ( + // Lints is a map of all known lints by name. Add a Lint to the map by calling + // RegisterLint. + Lints = make(map[string]*Lint) +) + +// RegisterLint must be called once for each lint to be excuted. Duplicate lint +// names are squashed. Normally, RegisterLint is called during init(). +func RegisterLint(l *Lint) { + if err := l.Lint.Initialize(); err != nil { + panic("could not initialize lint: " + l.Name + ": " + err.Error()) + } + Lints[l.Name] = l +} From b59a68b0af55462fb1bdf5c29c9ce25d9b8f22be Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 20 Jan 2020 14:13:25 -0500 Subject: [PATCH 2/5] zlint: refactor lint reg., allow filtering lints used. This replaces the exported `lint.LintMap` field (`map[string]*Lint`) that was used by `RegisterLint` with a more robust solution based around a `Registry` interface. This allows ZLint users to include/exclude lints by name or source. Top Level API ------------- The `lint.RegisterLint` function remains the same, meaning individual lints are not changed. Lints that call this function are added to the global registry. Clients can access this registry with `lint.GlobalRegistry()`. Similarly the top level `zlint.LintCertificate` remains unchanged. It lints the provided cert with all lints in the global registry. The old `zlint.LintCertificateFiltered` function that accepted a lint name regex to filter the lints applied is replaced by a new function `zlint.LintCertificateEx` that allows specifying a `lint.Registry` explicitly. The `lint.Source` type was changed from an int enum to a string enum. This makes it easier to work with as a consumer (e.g. via command line flags, and JSON output) and since the number of lints (and sources) is small the benefits to using an int enum type are minimal. The serialized form of Lints now includes the `Source` field in the output as `"source"`. Registry -------- The `Registry` interface also allows finding all lint names with `Names()`, finding all lint sources with `Sources()`, finding a specific lint by name with `ByName()`, and finding all lints for a given source with `BySource()`. The `zlint.EncodeLintDescriptionsToJSON` function is now implemented by the `Registry` interface as `WriteJSON`. This makes it easier to encode a subset of the Registry's lints by filtering the global registry. Like before (with the exported `map[string]*Lint`) the registry is not safe for concurrent updates. That's fine for the current ZLint codebase but is something we may want to consider addressing in the future. Registry Filtering ------------------ Filtering of lints to be run is now done with the `lint.Registry.Filter` function and corresponding `lint.FilterOptions` type. This allows filtering a registry to include/exclude lints by name (or using a name regex), and to include/exclude lints by source. By filtering the global registry and then providing it explicitly to `zlint.LintCertificateEx` callers have control over exactly what lints will be applied. Filtering operations are applied with the following precedence: excludes by source > includes by source > excludes by name > includes by name. E.g. excluding a source and then trying to include a lint in that excluded source by name will not work. The source exclusion happens first. ZLint CMD Updates ----------------- The `zlint` command (`cmd/zlint/main.go`) is updated to add three new command line flags: 1. `-list-lints-sources` - Prints a list of lint sources, one per line. 2. `-excludeSources` - Comma-separated list of lint sources to exclude. 3. `-includeSources` - Comma-separated list of lint sources to include. 4. `-nameFilter` - Regex used to match lint names to include (cannot be used at the same time as `-excludeSources` or `-includeSources) Two existing flags are renamed: 1. `-include` becomes `-includeNames` 2. `-exclude` becomes `-excludeNames`. Notably all three list flags (`-list-lints-json`, `-list-lints-schema` and `-list-lints-sources`) now operate **after** applying the include/exclude filters, allowing an easy way to find which lints/sources will be run with the filtered command line flags in use. Integration Test Updates ------------------------ Matching the `zlint` command the integration test (`integration/integration_test.go`) command line flags are updated to allow including/excluding lints by source. --- benchmarks_test.go | 17 +- cmd/zlint/main.go | 131 ++++--- integration/README.md | 14 +- integration/corpus_test.go | 2 +- integration/integration_test.go | 32 ++ lint/base.go | 2 +- lint/registration.go | 338 ++++++++++++++++- lint/registration_test.go | 356 +++++++++++++++++- lint/source.go | 142 +++++-- lint/source_test.go | 59 +++ lints/registration.go | 30 -- ..._ext_cert_policy_explicit_text_not_utf8.go | 2 + lints/template_test.go | 93 +++-- makefile | 2 + resultset.go | 58 +++ test/helpers.go | 6 +- zlint.go | 81 +--- zlint_test.go | 2 +- 18 files changed, 1113 insertions(+), 254 deletions(-) create mode 100644 lint/source_test.go delete mode 100644 lints/registration.go create mode 100644 resultset.go diff --git a/benchmarks_test.go b/benchmarks_test.go index beb8bd8f8..f801cd66d 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -109,18 +109,20 @@ func BenchmarkZlint(b *testing.B) { globalLintResult = lintResult }) + names := lint.GlobalRegistry().Names() + b.Run("Fast lints", func(b *testing.B) { globalLintResult = &ResultSet{} - globalLintResult.Results = make(map[string]*lint.LintResult, len(lint.Lints)) + globalLintResult.Results = make(map[string]*lint.LintResult, len(names)) b.ResetTimer() for i := 0; i < b.N; i++ { - for key, value := range lint.Lints { + for _, key := range names { switch key { case "w_dnsname_underscore_in_trd", "e_dnsname_underscore_in_sld", "e_dnsname_hyphen_in_sld", "w_dnsname_wildcard_left_of_public_suffix", "w_san_iana_pub_suffix_empty": continue } - + value := lint.GlobalRegistry().ByName(key) if !value.Lint.CheckApplies(x509Cert) { continue } @@ -131,10 +133,10 @@ func BenchmarkZlint(b *testing.B) { b.Run("Fastest lints", func(b *testing.B) { globalLintResult = &ResultSet{} - globalLintResult.Results = make(map[string]*lint.LintResult, len(lint.Lints)) + globalLintResult.Results = make(map[string]*lint.LintResult, len(names)) b.ResetTimer() for i := 0; i < b.N; i++ { - for key, value := range lint.Lints { + for _, key := range names { switch key { case "w_dnsname_underscore_in_trd", "e_dnsname_underscore_in_sld", "e_dnsname_hyphen_in_sld", "w_dnsname_wildcard_left_of_public_suffix", "w_san_iana_pub_suffix_empty", @@ -148,7 +150,7 @@ func BenchmarkZlint(b *testing.B) { "e_path_len_constraint_zero_or_less", "e_san_dns_name_includes_null_char": continue } - + value := lint.GlobalRegistry().ByName(key) if !value.Lint.CheckApplies(x509Cert) { continue } @@ -157,8 +159,9 @@ func BenchmarkZlint(b *testing.B) { } }) - for key, value := range lint.Lints { + for _, key := range names { b.Run(key, func(b *testing.B) { + value := lint.GlobalRegistry().ByName(key) if !value.Lint.CheckApplies(x509Cert) { b.Skip("Check doesn't apply") } diff --git a/cmd/zlint/main.go b/cmd/zlint/main.go index f779d889b..4db0ff6c9 100644 --- a/cmd/zlint/main.go +++ b/cmd/zlint/main.go @@ -23,6 +23,7 @@ import ( "fmt" "io/ioutil" "os" + "regexp" "sort" "strings" @@ -35,18 +36,26 @@ import ( var ( // flags listLintsJSON bool listLintsSchema bool + listLintSources bool prettyprint bool format string - include string - exclude string + nameFilter string + includeNames string + excludeNames string + includeSources string + excludeSources string ) func init() { - flag.BoolVar(&listLintsJSON, "list-lints-json", false, "Print supported lints in JSON format, one per line") - flag.BoolVar(&listLintsSchema, "list-lints-schema", false, "Print supported lints as a ZSchema") + flag.BoolVar(&listLintsJSON, "list-lints-json", false, "Print lints in JSON format, one per line") + flag.BoolVar(&listLintsSchema, "list-lints-schema", false, "Print lints as a ZSchema") + flag.BoolVar(&listLintSources, "list-lints-source", false, "Print list of lint sources, one per line") flag.StringVar(&format, "format", "pem", "One of {pem, der, base64}") - flag.StringVar(&include, "include", "", "Comma-separated list of lints to include by name") - flag.StringVar(&exclude, "exclude", "", "Comma-separated list of lints to exclude by name") + flag.StringVar(&nameFilter, "nameFilter", "", "Only run lints with a name matching the provided regex. (Can not be used with -includeNames/-excludeNames)") + flag.StringVar(&includeNames, "includeNames", "", "Comma-separated list of lints to include by name") + flag.StringVar(&excludeNames, "excludeNames", "", "Comma-separated list of lints to exclude by name") + flag.StringVar(&includeSources, "includeSources", "", "Comma-separated list of lint sources to include") + flag.StringVar(&excludeSources, "excludeSources", "", "Comma-separated list of lint sources to exclude") flag.BoolVar(&prettyprint, "pretty", false, "Pretty-print output") flag.Usage = func() { @@ -58,32 +67,40 @@ func init() { } func main() { + // Build a registry of lints using the include/exclude lint name and source + // flags. + registry, err := setLints() + if err != nil { + log.Fatalf("unable to configure included/exclude lints: %v\n", err) + } if listLintsJSON { - zlint.EncodeLintDescriptionsToJSON(os.Stdout) + registry.WriteJSON(os.Stdout) return } if listLintsSchema { - names := make([]string, 0, len(lint.Lints)) - for lintName := range lint.Lints { - names = append(names, lintName) - } - sort.Strings(names) + names := registry.Names() fmt.Printf("Lints = SubRecord({\n") for _, lintName := range names { - fmt.Printf(" \"%s\":LintBool(),\n", lintName) + fmt.Printf(" %q:LintBool(),\n", lintName) } fmt.Printf("})\n") return } - // include/exclude lints based on flags - setLints() + if listLintSources { + sources := registry.Sources() + sort.Sort(sources) + for _, source := range sources { + fmt.Printf(" %s\n", source) + } + return + } var inform = strings.ToLower(format) if flag.NArg() < 1 || flag.Arg(0) == "-" { - doLint(os.Stdin, inform) + doLint(os.Stdin, inform, registry) } else { for _, filePath := range flag.Args() { var inputFile *os.File @@ -100,13 +117,13 @@ func main() { fileInform = "pem" } - doLint(inputFile, fileInform) + doLint(inputFile, fileInform, registry) inputFile.Close() } } } -func doLint(inputFile *os.File, inform string) { +func doLint(inputFile *os.File, inform string, registry lint.Registry) { fileBytes, err := ioutil.ReadAll(inputFile) if err != nil { log.Fatalf("unable to read file %s: %s", inputFile.Name(), err) @@ -136,7 +153,7 @@ func doLint(inputFile *os.File, inform string) { log.Fatalf("unable to parse certificate: %s", err) } - zlintResult := zlint.LintCertificate(c) + zlintResult := zlint.LintCertificateEx(c, registry) jsonBytes, err := json.Marshal(zlintResult.Results) if err != nil { log.Fatalf("unable to encode lints JSON: %s", err) @@ -154,58 +171,50 @@ func doLint(inputFile *os.File, inform string) { os.Stdout.Sync() } -func setLints() { - if include != "" && exclude != "" { - log.Fatal("unable to use include and exclude flag at the same time") +// trimmedList takes a comma separated string argument in raw, splits it by +// comma, and returns a list of the separated elements after trimming spaces +// from each element. +func trimmedList(raw string) []string { + var list []string + for _, item := range strings.Split(raw, ",") { + list = append(list, strings.TrimSpace(item)) } - - includeLints() - excludeLints() + return list } -func includeLints() { - if include == "" { - return +// setLints returns a filtered registry to use based on the nameFilter, +// includeNames, excludeNames, includeSources, and excludeSources flag values in +// use. +func setLints() (lint.Registry, error) { + // If there's no filter options set, use the global registry as-is + if nameFilter == "" && includeNames == "" && excludeNames == "" && includeSources == "" && excludeSources == "" { + return nil, nil } - // parse includes to map for easier matching - var includes = strings.Split(include, ",") - var includesMap = make(map[string]struct{}, len(includes)) - for _, includeName := range includes { - includeName = strings.TrimSpace(includeName) - if _, ok := lint.Lints[includeName]; !ok { - log.Fatalf("unknown lint %q in include list", includeName) + filterOpts := lint.FilterOptions{} + if nameFilter != "" { + r, err := regexp.Compile(nameFilter) + if err != nil { + return nil, fmt.Errorf("bad -nameFilter: %v", err) } - - includesMap[includeName] = struct{}{} + filterOpts.NameFilter = r } - - // clear all initialized lints except for includes - for lintName := range lint.Lints { - if _, ok := includesMap[lintName]; !ok { - delete(lint.Lints, lintName) + if excludeSources != "" { + if err := filterOpts.ExcludeSources.FromString(excludeSources); err != nil { + log.Fatalf("invalid -excludeSources: %v", err) } } -} - -func excludeLints() { - if exclude == "" { - return + if includeSources != "" { + if err := filterOpts.IncludeSources.FromString(includeSources); err != nil { + log.Fatalf("invalid -includeSources: %v\n", err) + } } - - // parse excludes to map to get rid of duplicates - var excludes = strings.Split(exclude, ",") - var excludesMap = make(map[string]struct{}, len(excludes)) - for _, excludeName := range excludes { - excludesMap[strings.TrimSpace(excludeName)] = struct{}{} + if excludeNames != "" { + filterOpts.ExcludeNames = trimmedList(excludeNames) } - - // exclude lints - for excludeName := range excludesMap { - if _, ok := lint.Lints[excludeName]; !ok { - log.Fatalf("unknown lint %q in exclude list", excludeName) - } - - delete(lint.Lints, excludeName) + if includeNames != "" { + filterOpts.IncludeNames = trimmedList(includeNames) } + + return lint.GlobalRegistry().Filter(filterOpts) } diff --git a/integration/README.md b/integration/README.md index 593a78d46..0e01d8da8 100644 --- a/integration/README.md +++ b/integration/README.md @@ -46,19 +46,23 @@ Config options * `-parallelism` - number of linting Go routines to spawn (_Default: 5_) -* `-configFile` - integration test config file (_Default `integration/config.json`_) +* `-config` - integration test config file (_Default `integration/config.json`_) * `-forceDownload` - ignore cached data files on disk forcing it to be downloaded fresh (_Default false_) * `-overwriteExpected` - overwrite the expected results map in the `-configFile` with the results of the test run. This is useful when new lints or bugfixes are added and the changes in the results map have been vetted and are ready to be committed to the repository. (_Default false_) -* `-fingerprintSummarize` - print a summary of all certificate fingerprints that had lint findings. Can be quite spammy with the default data set. (_Default false_) +* `-fingerprintSummary` - print a summary of all certificate fingerprints that had lint findings. Can be quite spammy with the default data set. (_Default false_) -* `-fingerprintFilterString` - only lint certificates with hex encoded fingerprints that match the provided regular expression (_Default none_) +* `-fingerprintFilter` - only lint certificates with hex encoded fingerprints that match the provided regular expression (_Default none_) -* `-lintSummarize` - print a summary of result type counts by lint name. (_Default false_) +* `-lintSummary` - print a summary of result type counts by lint name. (_Default false_) -* `-lintFilterString` - only lint certificates with lints that have a name that matches the provided regular expression (_Default: none_) +* `-lintFilter` - only lint certificates with lints that have a name that matches the provided regular expression (_Default: none_) + +* `-includeSources` - only lint certificates with lints that specify a Source present in the comma separated list provided (case sensitive) (_Default: none_) + +* `-excludeSources` - only lint certificates with lints that do not specify a Source present in the comma separated list provided (case sensitive) (_Default: none_) * `-outputTick` - number of certificates to lint before printing a "." marker to output (_Default 1000_) diff --git a/integration/corpus_test.go b/integration/corpus_test.go index 7907fd1d8..6798c9595 100644 --- a/integration/corpus_test.go +++ b/integration/corpus_test.go @@ -24,7 +24,7 @@ func lintCertificate(work workItem) certResult { Fingerprint: work.Fingerprint, LintSummary: make(map[string]lint.LintStatus), } - resultSet := zlint.LintCertificateFiltered(work.Certificate, lintFilter) + resultSet := zlint.LintCertificateEx(work.Certificate, registry) for lintName, r := range resultSet.Results { cr.LintSummary[lintName] = r.Status cr.Result.Inc(r.Status) diff --git a/integration/integration_test.go b/integration/integration_test.go index 59e4f1807..88b4a589a 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -8,6 +8,8 @@ import ( "os" "regexp" "testing" + + "github.com/zmap/zlint/lint" ) var ( @@ -37,6 +39,8 @@ var ( // lintFilterString is a flag for controlling which lints are run against the test // corpus. lintFilterString = flag.String("lintFilter", "", "if not-empty only lints with a name that match the provided regexp will be run") + includeSources = flag.String("includeSources", "", "Comma-separated list of lint sources to include") + excludeSources = flag.String("excludeSources", "", "Comma-separated list of lint sources to exclude") // outputTick is a flag for controlling the number of certificates that are // linted before a '.' is printed in the console. This controls the mechanism // used to keep Travis from thinking the job is dead because there hasn't been @@ -52,6 +56,9 @@ var ( // fpFilter and lintFilter are regexps for filtering certificate fingerprints // to be linted and lints to be run. fpFilter, lintFilter *regexp.Regexp + + // registry is the lint registry used. It may be filtered based on command line flags. + registry = lint.GlobalRegistry() ) // TestMain loads the integration test config, validates it, and prepares the @@ -84,6 +91,31 @@ func TestMain(m *testing.M) { log.Fatalf("error processing config file %q: %v", *configFile, err) } + // Configure filter options + filterOpts := lint.FilterOptions{} + if *excludeSources != "" { + if err := filterOpts.ExcludeSources.FromString(*excludeSources); err != nil { + log.Fatalf("invalid -excludeSources: %v", err) + } + } + if *includeSources != "" { + if err := filterOpts.IncludeSources.FromString(*includeSources); err != nil { + log.Fatalf("invalid -includeSources: %v\n", err) + } + } + if lintFilter != nil { + filterOpts.NameFilter = lintFilter + } + + // If there were filter options configured apply them and update the registry + if !filterOpts.Empty() { + r, err := registry.Filter(filterOpts) + if err != nil { + log.Fatalf("failed to filter lint registry: %v\n", err) + } + registry = r + } + // Prepare cache, downloading data files if required (or if forced by user // request with forceDownload) if err := c.PrepareCache(*forceDownload); err != nil { diff --git a/lint/base.go b/lint/base.go index 4335638cf..dc435f491 100644 --- a/lint/base.go +++ b/lint/base.go @@ -54,7 +54,7 @@ type Lint struct { Citation string `json:"citation,omitempty"` // Programmatic source of the check, BRs, RFC5280, or ZLint - Source LintSource `json:"-"` + Source LintSource `json:"source"` // Lints automatically returns NE for all certificates where CheckApplies() is // true but with NotBefore < EffectiveDate. This check is bypassed if diff --git a/lint/registration.go b/lint/registration.go index 24125174c..e5c639091 100644 --- a/lint/registration.go +++ b/lint/registration.go @@ -1,16 +1,338 @@ +/* + * ZLint Copyright 2020 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + package lint +import ( + "encoding/json" + "errors" + "fmt" + "io" + "regexp" + "sort" + "strings" +) + +// FilterOptions is a struct used by Registry.Filter to create a sub registry +// containing only lints that meet the filter options specified. +// +// Source based exclusion/inclusion is evaluated before Lint name based +// exclusion/inclusion. In both cases exclusion is processed before inclusion. +// +// Only one of NameFilter or IncludeNames/ExcludeNames can be provided at +// a time. +type FilterOptions struct { + // NameFilter is a regexp used to filter lints by their name. It is mutually + // exclusive with IncludeNames and ExcludeNames. + NameFilter *regexp.Regexp + // IncludeNames is a case sensitive list of lint names to include in the + // registry being filtered. + IncludeNames []string + // ExcludeNames is a case sensitive list of lint names to exclude from the + // registry being filtered. + ExcludeNames []string + // IncludeSource is a SourceList of LintSource's to be included in the + // registry being filtered. + IncludeSources SourceList + // ExcludeSources is a SourceList of LintSources's to be excluded in the + // registry being filtered. + ExcludeSources SourceList +} + +// Empty returns true if the FilterOptions is empty and does not specify any +// elements to filter by. +func (opts FilterOptions) Empty() bool { + return opts.NameFilter == nil && + len(opts.IncludeNames) == 0 && + len(opts.ExcludeNames) == 0 && + len(opts.IncludeSources) == 0 && + len(opts.ExcludeSources) == 0 +} + +// Registry is an interface describing a collection of registered lints. +// A Registry instance can be given to zlint.LintCertificateEx() to control what +// lints are run for a given certificate. +// +// Typically users will interact with the global Registry returned by +// GlobalRegistry(), or a filtered Registry created by applying FilterOptions to +// the GlobalRegistry()'s Filter function. +type Registry interface { + // Names returns a list of all of the lint names that have been registered + // in string sorted order. + Names() []string + // Sources returns a SourceList of registered LintSources. The list is not + // sorted but can be sorted by the caller with sort.Sort() if required. + Sources() SourceList + // ByName returns a pointer to the registered lint with the given name, or nil + // if there is no such lint registered in the registry. + ByName(name string) *Lint + // BySource returns a list of registered lints that have the same LintSource as + // provided (or nil if there were no such lints in the registry). + BySource(s LintSource) []*Lint + // Filter returns a new Registry containing only lints that match the + // FilterOptions criteria. + Filter(opts FilterOptions) (Registry, error) + // WriteJSON writes a description of each registered lint as + // a JSON object, one object per line, to the provided writer. + WriteJSON(w io.Writer) +} + +// registryImpl implements the Registry interface to provide a global collection +// of Lints that have been registered. +type registryImpl struct { + // lintsByName is a map of all registered lints by name. + lintsByName map[string]*Lint + // lintNames is a sorted list of all of the registered lint names. It is + // equivalent to collecting the keys from lintsByName into a slice and sorting + // them lexicographically. + lintNames []string + // lintsBySource is a map of all registered lints by source category. Lints + // are added to the lintsBySource map by RegisterLint. + lintsBySource map[LintSource][]*Lint +} + var ( - // Lints is a map of all known lints by name. Add a Lint to the map by calling - // RegisterLint. - Lints = make(map[string]*Lint) + // errNilLint is returned from registry.Register if the provided lint was nil. + errNilLint = errors.New("can not register a nil lint") + // errNilLintPtr is returned from registry.Register if the provided lint had + // a nil Lint field. + errNilLintPtr = errors.New("can not register a lint with a nil Lint pointer") + // errEmptyName is returned from registry.Register if the provided lint had an + // empty Name field. + errEmptyName = errors.New("can not register a lint with an empty Name") ) -// RegisterLint must be called once for each lint to be excuted. Duplicate lint -// names are squashed. Normally, RegisterLint is called during init(). +// errDuplicateName is returned from registry.Register if the provided lint had +// a Name field matching a lint that was previously registered. +type errDuplicateName struct { + lintName string +} + +func (e errDuplicateName) Error() string { + return fmt.Sprintf( + "can not register lint with name %q - it has already been registered", + e.lintName) +} + +// errBadInit is returned from registry.Register if the provided lint's +// Initialize function returned an error. +type errBadInit struct { + lintName string + err error +} + +func (e errBadInit) Error() string { + return fmt.Sprintf( + "failed to register lint with name %q - failed to Initialize: %q", + e.lintName, e.err) +} + +// register adds the provided lint to the Registry. If initialize is true then +// the lint's Initialize() function will be called before registering the lint. +// +// An error is returned if the lint or lint's Lint pointer is nil, if the Lint +// has an empty Name or if the Name was previously registered. +func (r *registryImpl) register(l *Lint, initialize bool) error { + if l == nil { + return errNilLint + } + if l.Lint == nil { + return errNilLintPtr + } + if l.Name == "" { + return errEmptyName + } + if existing := r.lintsByName[l.Name]; existing != nil { + return &errDuplicateName{l.Name} + } + if initialize { + if err := l.Lint.Initialize(); err != nil { + return &errBadInit{l.Name, err} + } + } + r.lintNames = append(r.lintNames, l.Name) + r.lintsByName[l.Name] = l + r.lintsBySource[l.Source] = append(r.lintsBySource[l.Source], l) + sort.Strings(r.lintNames) + return nil +} + +// ByName returns the Lint previously registered under the given name with +// Register, or nil if no matching lint name has been registered. +func (r *registryImpl) ByName(name string) *Lint { + return r.lintsByName[name] +} + +// Names returns a list of all of the lint names that have been registered +// in string sorted order. +func (r *registryImpl) Names() []string { + return r.lintNames +} + +// BySource returns a list of registered lints that have the same LintSource as +// provided (or nil if there were no such lints). +func (r *registryImpl) BySource(s LintSource) []*Lint { + return r.lintsBySource[s] +} + +// Sources returns a SourceList of registered LintSources. The list is not +// sorted but can be sorted by the caller with sort.Sort() if required. +func (r *registryImpl) Sources() SourceList { + var results SourceList + for k := range r.lintsBySource { + results = append(results, k) + } + return results +} + +// lintNamesToMap converts a list of lit names into a bool hashmap useful for +// filtering. If any of the lint names are not known by the registry an error is +// returned. +func (r *registryImpl) lintNamesToMap(names []string) (map[string]bool, error) { + if len(names) == 0 { + return nil, nil + } + + namesMap := make(map[string]bool, len(names)) + for _, n := range names { + n = strings.TrimSpace(n) + if l := r.ByName(n); l == nil { + return nil, fmt.Errorf("unknown lint name %q", n) + } + namesMap[n] = true + } + return namesMap, nil +} + +func sourceListToMap(sources SourceList) map[LintSource]bool { + if len(sources) == 0 { + return nil + } + sourceMap := make(map[LintSource]bool, len(sources)) + for _, s := range sources { + sourceMap[s] = true + } + return sourceMap +} + +// Filter creates a new Registry with only the lints that meet the FilterOptions +// criteria included. +// +// FilterOptions are applied in the following order of precedence: +// ExcludeSources > IncludeSources > NameFilter > ExcludeNames > IncludeNames +func (r *registryImpl) Filter(opts FilterOptions) (Registry, error) { + // If there's no filtering to be done, return the existing Registry. + if opts.Empty() { + return r, nil + } + + filteredRegistry := newRegistry() + + sourceExcludes := sourceListToMap(opts.ExcludeSources) + sourceIncludes := sourceListToMap(opts.IncludeSources) + + nameExcludes, err := r.lintNamesToMap(opts.ExcludeNames) + if err != nil { + return nil, err + } + nameIncludes, err := r.lintNamesToMap(opts.IncludeNames) + if err != nil { + return nil, err + } + + if opts.NameFilter != nil && (len(nameExcludes) != 0 || len(nameIncludes) != 0) { + return nil, errors.New( + "FilterOptions.NameFilter cannot be used at the same time as " + + "FilterOptions.ExcludeNames or FilterOptions.IncludeNames") + } + + for _, name := range r.Names() { + l := r.ByName(name) + + if sourceExcludes != nil && sourceExcludes[l.Source] { + continue + } + if sourceIncludes != nil && !sourceIncludes[l.Source] { + continue + } + if opts.NameFilter != nil && !opts.NameFilter.MatchString(name) { + continue + } + if nameExcludes != nil && nameExcludes[name] { + continue + } + if nameIncludes != nil && !nameIncludes[name] { + continue + } + + // when adding lints to a filtered registry we do not want Initialize() to + // be called a second time, so provide false as the initialize argument. + if err := filteredRegistry.register(l, false); err != nil { + return nil, err + } + } + + return filteredRegistry, nil +} + +// WriteJSON writes a description of each registered lint as +// a JSON object, one object per line, to the provided writer. +func (r *registryImpl) WriteJSON(w io.Writer) { + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + for _, name := range r.Names() { + _ = enc.Encode(r.ByName(name)) + } +} + +// newRegistry constructs a registryImpl that can be used to register lints. +func newRegistry() *registryImpl { + return ®istryImpl{ + lintsByName: make(map[string]*Lint), + lintsBySource: make(map[LintSource][]*Lint), + } +} + +// globalRegistry is the Registry used by all loaded lints that call +// RegisterLint(). +var globalRegistry *registryImpl = newRegistry() + +// RegisterLint must be called once for each lint to be executed. Normally, +// RegisterLint is called from the Go init() function of a lint implementation. +// +// RegsterLint will call l.Lint's Initialize() function as part of the +// registration process. +// +// IMPORTANT: RegisterLint will panic if given a nil lint, or a lint with a nil +// Lint pointer, or if the lint's Initialize function errors, or if the lint +// name matches a previously registered lint's name. These conditions all +// indicate a bug that should be addressed by a developer. func RegisterLint(l *Lint) { - if err := l.Lint.Initialize(); err != nil { - panic("could not initialize lint: " + l.Name + ": " + err.Error()) + // RegisterLint always sets initialize to true. It's assumed this is called by + // the package init() functions and therefore must be doing the first + // initialization of a lint. + if err := globalRegistry.register(l, true); err != nil { + panic(fmt.Sprintf("RegisterLint error: %v\n", err.Error())) } - Lints[l.Name] = l +} + +// GlobalRegistry is the Registry used by RegisterLint and contains all of the +// lints that are loaded. +// +// If you want to run only a subset of the globally registered lints use +// GloablRegistry().Filter with FilterOptions to create a filtered +// Registry. +func GlobalRegistry() Registry { + return globalRegistry } diff --git a/lint/registration_test.go b/lint/registration_test.go index a066e78ff..ec1c5844c 100644 --- a/lint/registration_test.go +++ b/lint/registration_test.go @@ -14,10 +14,19 @@ package lint * permissions and limitations under the License. */ -import "testing" +import ( + "errors" + "reflect" + "regexp" + "sort" + "testing" + + "github.com/zmap/zcrypto/x509" +) func TestAllLintsHaveNameDescriptionSource(t *testing.T) { - for name, lint := range Lints { + for _, name := range GlobalRegistry().Names() { + lint := GlobalRegistry().ByName(name) if lint.Name == "" { t.Errorf("lint %s has empty name", name) } @@ -31,9 +40,350 @@ func TestAllLintsHaveNameDescriptionSource(t *testing.T) { } func TestAllLintsHaveSource(t *testing.T) { - for name, lint := range Lints { + for _, name := range globalRegistry.Names() { + lint := GlobalRegistry().ByName(name) if lint.Source == UnknownLintSource { t.Errorf("lint %s has unknown source", name) } } } + +func TestFilterOptionsEmpty(t *testing.T) { + opts := FilterOptions{} + if !opts.Empty() { + t.Errorf("Empty FilterOptions wasn't Empty()") + } + opts.IncludeNames = []string{"whatever"} + if opts.Empty() { + t.Errorf("Non-empty FilterOptions was Empty()") + } +} + +type mockLint struct { + initErr error +} + +func (m mockLint) Initialize() error { + return m.initErr +} + +func (m mockLint) CheckApplies(c *x509.Certificate) bool { + return true +} + +func (m mockLint) Execute(c *x509.Certificate) *LintResult { + return nil +} + +func TestRegister(t *testing.T) { + egLint := &Lint{ + Name: "mockLint", + Lint: &mockLint{}, + Source: ZLint, + } + dupeReg := newRegistry() + _ = dupeReg.register(egLint, true) + + badInitErr := errors.New("mock init error") + badInitLint := &Lint{ + Name: "badInitLint", + Lint: &mockLint{badInitErr}, + Source: ZLint, + } + + testCases := []struct { + name string + lint *Lint + init bool + registry *registryImpl + expectErr error + expectNames []string + expectSources SourceList + }{ + { + name: "nil lint", + lint: nil, + expectErr: errNilLint, + }, + { + name: "nil lint ptr", + lint: &Lint{}, + expectErr: errNilLintPtr, + }, + { + name: "empty name", + lint: &Lint{ + Lint: &mockLint{}, + }, + expectErr: errEmptyName, + }, + { + name: "duplicate name", + lint: egLint, + registry: dupeReg, + expectErr: &errDuplicateName{egLint.Name}, + }, + { + name: "bad init with initialize", + lint: badInitLint, + init: true, + expectErr: &errBadInit{badInitLint.Name, badInitErr}, + }, + { + name: "bad init with no initialize", + lint: badInitLint, + init: false, + expectNames: []string{badInitLint.Name}, + expectSources: SourceList{badInitLint.Source}, + }, + { + name: "good lint register", + lint: &Lint{ + Name: "goodLint", + Lint: &mockLint{}, + Source: MozillaRootStorePolicy, + }, + registry: dupeReg, + expectNames: []string{"goodLint", egLint.Name}, + expectSources: SourceList{MozillaRootStorePolicy, egLint.Source}, + }, + } + + for _, tc := range testCases { + var reg *registryImpl + if tc.registry == nil { + reg = newRegistry() + } else { + reg = tc.registry + } + + err := reg.register(tc.lint, tc.init) + if err == nil && tc.expectErr != nil { + t.Errorf("expected err %v, got nil", tc.expectErr) + } else if err != nil && err.Error() != tc.expectErr.Error() { + t.Errorf("expected err %v got %v", tc.expectErr, err) + } else if err == nil { + if !reflect.DeepEqual(reg.Names(), tc.expectNames) { + t.Errorf("expected names %v, got %v", tc.expectNames, reg.Names()) + } + sources := reg.Sources() + sort.Sort(sources) + if !reflect.DeepEqual(sources, tc.expectSources) { + t.Errorf("expected sources %v, got %v", tc.expectSources, sources) + } + } + } +} + +func TestRegistryFilter(t *testing.T) { + testLint := func(name string, source LintSource) *Lint { + return &Lint{ + Name: name, + Source: source, + Lint: &mockLint{}, + } + } + mustRegister := func(r *registryImpl, l *Lint) { + if err := r.register(l, true); err != nil { + t.Fatalf("failed to register %v", err) + } + } + + // Create a registry and add some test lints + registry := newRegistry() + + mustRegister(registry, testLint("e_mp_example1", MozillaRootStorePolicy)) + mustRegister(registry, testLint("w_mp_example2", MozillaRootStorePolicy)) + mustRegister(registry, testLint("n_mp_example3", MozillaRootStorePolicy)) + mustRegister(registry, testLint("e_z_example1", ZLint)) + mustRegister(registry, testLint("e_rfc_example1", RFC5280)) + mustRegister(registry, testLint("w_rfc_example2", RFC5280)) + + onlyWarnRegex := regexp.MustCompile(`^w\_.*`) + + // Up front, test that invalid FilterOptions provokes an err + _, err := registry.Filter(FilterOptions{ + NameFilter: onlyWarnRegex, + IncludeNames: []string{"e_mp_example_1"}, + }) + if err == nil { + t.Errorf("expected err from invalid FilterOptions, got nil") + } + + testCases := []struct { + name string + opts FilterOptions + expectedLintNames []string + expectedSources SourceList + }{ + { + name: "Empty filter options", + expectedLintNames: []string{ + "e_mp_example1", "e_rfc_example1", "e_z_example1", "n_mp_example3", "w_mp_example2", "w_rfc_example2", + }, + expectedSources: SourceList{ + MozillaRootStorePolicy, RFC5280, ZLint, + }, + }, + { + name: "Filter by NameFilter only", + opts: FilterOptions{ + NameFilter: onlyWarnRegex, + }, + expectedLintNames: []string{ + "w_mp_example2", "w_rfc_example2", + }, + expectedSources: SourceList{ + MozillaRootStorePolicy, RFC5280, + }, + }, + { + name: "Filter by IncludeNames only", + opts: FilterOptions{ + IncludeNames: []string{ + "e_rfc_example1", "w_mp_example2", + }, + }, + expectedLintNames: []string{ + "e_rfc_example1", "w_mp_example2", + }, + expectedSources: SourceList{ + MozillaRootStorePolicy, RFC5280, + }, + }, + { + name: "Filter by ExcludeNames only", + opts: FilterOptions{ + ExcludeNames: []string{ + "e_rfc_example1", "w_mp_example2", + }, + }, + expectedLintNames: []string{ + "e_mp_example1", "e_z_example1", "n_mp_example3", "w_rfc_example2", + }, + expectedSources: SourceList{ + MozillaRootStorePolicy, RFC5280, ZLint, + }, + }, + { + name: "Filter by ExcludeNames and IncludeNames", + opts: FilterOptions{ + ExcludeNames: []string{ + "e_rfc_example1", "w_mp_example2", + }, + IncludeNames: []string{ + "e_rfc_example1", "e_z_example1", + }, + }, + expectedLintNames: []string{ + "e_z_example1", + }, + expectedSources: SourceList{ + ZLint, + }, + }, + { + name: "Filter by IncludeSources only", + opts: FilterOptions{ + IncludeSources: SourceList{ + ZLint, RFC5280, + }, + }, + expectedLintNames: []string{ + "e_rfc_example1", "e_z_example1", "w_rfc_example2", + }, + expectedSources: SourceList{ + RFC5280, ZLint, + }, + }, + { + name: "Filter by ExcludeSources only", + opts: FilterOptions{ + ExcludeSources: SourceList{ + RFC5280, + }, + }, + expectedLintNames: []string{ + "e_mp_example1", "e_z_example1", "n_mp_example3", "w_mp_example2", + }, + expectedSources: SourceList{ + MozillaRootStorePolicy, ZLint, + }, + }, + { + name: "Filter by IncludeSources and ExcludeSources", + opts: FilterOptions{ + ExcludeSources: SourceList{ + RFC5280, + }, + IncludeSources: SourceList{ + ZLint, + }, + }, + expectedLintNames: []string{ + "e_z_example1", + }, + expectedSources: SourceList{ + ZLint, + }, + }, + { + name: "Filter by IncludeSources, ExcludeSources and NameFilter", + opts: FilterOptions{ + NameFilter: onlyWarnRegex, + ExcludeSources: SourceList{ + ZLint, + }, + IncludeSources: SourceList{ + MozillaRootStorePolicy, + RFC5280, + }, + }, + expectedLintNames: []string{ + "w_mp_example2", "w_rfc_example2", + }, + expectedSources: SourceList{ + MozillaRootStorePolicy, RFC5280, + }, + }, + { + name: "Filter by IncludeSources, ExcludeSources, IncludeNames and ExcludeNames", + opts: FilterOptions{ + ExcludeSources: SourceList{ + ZLint, + }, + IncludeSources: SourceList{ + MozillaRootStorePolicy, + RFC5280, + }, + ExcludeNames: []string{"e_mp_example1"}, + IncludeNames: []string{"e_rfc_example1", "w_mp_example2"}, + }, + expectedLintNames: []string{ + "e_rfc_example1", "w_mp_example2", + }, + expectedSources: SourceList{ + MozillaRootStorePolicy, RFC5280, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := registry.Filter(tc.opts) + if err != nil { + t.Fatalf("Filter returned err for %v", tc.opts) + } + + if !reflect.DeepEqual(result.Names(), tc.expectedLintNames) { + t.Errorf("expected post-Filter Names %v got %v", tc.expectedLintNames, result.Names()) + } + + sources := result.Sources() + sort.Sort(sources) + if !reflect.DeepEqual(sources, tc.expectedSources) { + t.Errorf("expected post-Filter Sources %v got %v", tc.expectedSources, sources) + } + }) + } +} diff --git a/lint/source.go b/lint/source.go index fccb17d8b..28834da9e 100644 --- a/lint/source.go +++ b/lint/source.go @@ -1,5 +1,11 @@ package lint +import ( + "encoding/json" + "fmt" + "strings" +) + /* * ZLint Copyright 2020 Regents of the University of Michigan * @@ -14,53 +20,113 @@ package lint * permissions and limitations under the License. */ -// An Enum to programmatically represent the source of a lint -type LintSource int +// LintSource is a type representing a known lint source that lints cite +// requirements from. +type LintSource string -// NOTE(@cpu): If you are adding a new LintSource make sure you have considered -// updating the Directory() function. const ( - UnknownLintSource LintSource = iota - CABFBaselineRequirements - RFC5280 - RFC5480 - RFC5891 - ZLint - AWSLabs - EtsiEsi // ETSI - Electronic Signatures and Infrastructures (ESI) - CABFEVGuidelines - AppleCTPolicy // https://support.apple.com/en-us/HT205280 - MozillaRootStorePolicy // https://github.com/mozilla/pkipolicy + UnknownLintSource LintSource = "Unknown" + RFC5280 LintSource = "RFC5280" + RFC5480 LintSource = "RFC5480" + RFC5891 LintSource = "RFC5891" + CABFBaselineRequirements LintSource = "CABF_BR" + CABFEVGuidelines LintSource = "CABF_EV" + MozillaRootStorePolicy LintSource = "Mozilla" + AppleCTPolicy LintSource = "Apple" + ZLint LintSource = "ZLint" + AWSLabs LintSource = "AWSLabs" + EtsiEsi LintSource = "ETSI_ESI" ) -// LintSources contains a list of the valid lint sources we expect to be used -// by ZLint lints. -var LintSources = []LintSource{ - CABFBaselineRequirements, - CABFEVGuidelines, - RFC5280, - RFC5480, - RFC5891, - AppleCTPolicy, - EtsiEsi, - ZLint, - AWSLabs, +// UnmarshalJSON implements the json.Unmarshaler interface. It ensures that the +// unmarshaled value is a known LintSource. +func (s *LintSource) UnmarshalJSON(data []byte) error { + var throwAway string + if err := json.Unmarshal(data, &throwAway); err != nil { + return err + } + + switch LintSource(throwAway) { + case RFC5280, RFC5480, RFC5891, CABFBaselineRequirements, CABFEVGuidelines, MozillaRootStorePolicy, AppleCTPolicy, ZLint, AWSLabs, EtsiEsi: + *s = LintSource(throwAway) + return nil + default: + *s = UnknownLintSource + return fmt.Errorf("unknown LintSource value %q", throwAway) + } } -// Directory returns the directory name in `lints/` for the LintSource. -func (l LintSource) Directory() string { - switch l { +// FromString sets the LintSource value based on the source string provided +// (case sensitive). If the src string does not match any of the known +// LintSource's then s is set to the UnknownLintSource. +func (s *LintSource) FromString(src string) { + // Start with the unknown lint source + *s = UnknownLintSource + // Trim space and try to match a known value + src = strings.TrimSpace(src) + switch LintSource(src) { + case RFC5280: + *s = RFC5280 + case RFC5480: + *s = RFC5480 + case RFC5891: + *s = RFC5891 case CABFBaselineRequirements: - return "cabf_br" + *s = CABFBaselineRequirements case CABFEVGuidelines: - return "cabf_ev" - case RFC5280, RFC5480, RFC5891: - return "rfc" + *s = CABFEVGuidelines + case MozillaRootStorePolicy: + *s = MozillaRootStorePolicy case AppleCTPolicy: - return "apple" + *s = AppleCTPolicy + case ZLint: + *s = ZLint + case AWSLabs: + *s = AWSLabs case EtsiEsi: - return "etsi" - default: - return "community" + *s = EtsiEsi + } +} + +// SourceList is a slice of LintSources that can be sorted. +type SourceList []LintSource + +// Len returns the length of the list. +func (l SourceList) Len() int { + return len(l) +} + +// Swap swaps the LintSource at index i and j in the list. +func (l SourceList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +// Less compares the LintSources at index i and j lexicographically. +func (l SourceList) Less(i, j int) bool { + return l[i] < l[j] +} + +// FromString populates a SourceList (replacing any existing content) with the +// comma separated list of sources provided in raw. If any of the comma +// separated values are not known LintSource's an error is returned. +func (l *SourceList) FromString(raw string) error { + // Start with an empty list + *l = SourceList{} + + values := strings.Split(raw, ",") + for _, val := range values { + val = strings.TrimSpace(val) + if val == "" { + continue + } + // Populate the LintSource with the trimmed value. + var src LintSource + src.FromString(val) + // If the LintSource is UnknownLintSource then return an error. + if src == UnknownLintSource { + return fmt.Errorf("unknown lint source in list: %q", val) + } + *l = append(*l, src) } + return nil } diff --git a/lint/source_test.go b/lint/source_test.go new file mode 100644 index 000000000..857bc40fa --- /dev/null +++ b/lint/source_test.go @@ -0,0 +1,59 @@ +/* + * ZLint Copyright 2020 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package lint + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" +) + +// TestLintSourceMarshal tests that a LintSource can be correctly marshaled and +// unmarshalled. +func TestLintSourceMarshal(t *testing.T) { + throwAway := struct { + Source LintSource + }{ + Source: ZLint, + } + + jsonBytes, err := json.Marshal(&throwAway) + if err != nil { + t.Fatalf("failed to marshal LintSource: %v", err) + } + + expectedJSON := fmt.Sprintf(`{"Source":%q}`, ZLint) + if !bytes.Equal(jsonBytes, []byte(expectedJSON)) { + t.Fatalf("expected JSON %q got %q", expectedJSON, string(jsonBytes)) + } + + err = json.Unmarshal(jsonBytes, &throwAway) + if err != nil { + t.Fatalf("err unmarshalling prev. marshaled LintSource: %v", err) + } + if throwAway.Source != ZLint { + t.Fatalf("expected post-unmarshal value of %q got %q", ZLint, throwAway.Source) + } + + badJSON := []byte(`{"Source":"cpu"}`) + err = json.Unmarshal(badJSON, &throwAway) + if err == nil { + t.Fatalf("expected err unmarshalling bad LintSource value. Got nil") + } + if throwAway.Source != UnknownLintSource { + t.Fatalf("expected Source to be %q after bad unmarshal, got %q\n", UnknownLintSource, throwAway.Source) + } +} diff --git a/lints/registration.go b/lints/registration.go deleted file mode 100644 index a6e054672..000000000 --- a/lints/registration.go +++ /dev/null @@ -1,30 +0,0 @@ -package lint - -/* - * ZLint Copyright 2020 Regents of the University of Michigan - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - * implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -var ( - // Lints is a map of all known lints by name. Add a Lint to the map by calling - // RegisterLint. - Lints = make(map[string]*Lint) -) - -// RegisterLint must be called once for each lint to be excuted. Duplicate lint -// names are squashed. Normally, RegisterLint is called during init(). -func RegisterLint(l *Lint) { - if err := l.Lint.Initialize(); err != nil { - panic("could not initialize lint: " + l.Name + ": " + err.Error()) - } - Lints[l.Name] = l -} diff --git a/lints/rfc/lint_ext_cert_policy_explicit_text_not_utf8.go b/lints/rfc/lint_ext_cert_policy_explicit_text_not_utf8.go index 6826311d8..795a8381f 100644 --- a/lints/rfc/lint_ext_cert_policy_explicit_text_not_utf8.go +++ b/lints/rfc/lint_ext_cert_policy_explicit_text_not_utf8.go @@ -15,6 +15,8 @@ package rfc */ /******************************************************************* +https://tools.ietf.org/html/rfc6818#section-3 + An explicitText field includes the textual statement directly in the certificate. The explicitText field is a string with a maximum size of 200 characters. Conforming CAs SHOULD use the diff --git a/lints/template_test.go b/lints/template_test.go index 98f072592..3354a59dc 100644 --- a/lints/template_test.go +++ b/lints/template_test.go @@ -2,18 +2,25 @@ package lints import ( "bytes" + "fmt" "io/ioutil" + "os" "path/filepath" "strings" "testing" +) - "github.com/zmap/zlint/lint" +var ( + // filesChecked is a global counter of the number of files tested by + // checkForLeftovers. + filesChecked int ) -// TestLeftoverTemplates tests that no .go files for each of the -// lint.LintSources contain leftovers from the new lint template that are -// intended to be replaced by the programmer. -func TestLeftoverTemplates(t *testing.T) { +// checkForLeftovers checks the given filename (assumed to be a .go src file) +// contains none of the template leftovers. An error is returned if there is +// a problem opening or reading the file, or if any template leftovers are +// found. +func checkForLeftovers(filename string) error { // See the `template` file in the root directory of ZLint. // None of these strings should appear outside of the template. They indicate // the programmer forgot to replace template text. @@ -25,32 +32,58 @@ func TestLeftoverTemplates(t *testing.T) { "Change this to match source TEXT", } - for _, lintSrc := range lint.LintSources { - files, err := ioutil.ReadDir(lintSrc.Directory()) - if err != nil { - t.Fatalf("Failed to read directory %q", lintSrc.Directory()) - } + src, err := ioutil.ReadFile(filename) + if err != nil { + return err + } - for _, f := range files { - // Skip non-Go files - if !strings.HasSuffix(f.Name(), ".go") { - continue - } - - srcPath := filepath.Join(lintSrc.Directory(), f.Name()) - src, err := ioutil.ReadFile(srcPath) - if err != nil { - t.Errorf("Failed to read src file %q: %v", - f.Name(), err) - continue - } - - for _, leftover := range leftovers { - if bytes.Contains(src, []byte(leftover)) { - t.Errorf("Lint %q contains template leftover %q", - srcPath, leftover) - } - } + filesChecked++ + for _, leftover := range leftovers { + if bytes.Contains(src, []byte(leftover)) { + return fmt.Errorf( + "file %q contains template leftover %q", + filename, leftover) } } + + return nil +} + +// checkFile is a filepath.WalkFunc handler that checks .go files for leftovers. +func checkFile(path string, info os.FileInfo, err error) error { + // Abort on any incoming errs from filepath.Walk + if err != nil { + return err + } + // Don't check directories + if info.IsDir() { + return nil + } + // Only check .go files + if !strings.HasSuffix(path, ".go") { + return nil + } + // Don't check the template test file, it has the strings we're checking for + // by design! + if strings.HasSuffix(path, "template_test.go") { + return nil + } + + // Check the path for leftovers + return checkForLeftovers(path) +} + +// TestLeftoverTemplates tests that no .go files under the current directory +// contain leftovers from the new lint template that are intended to be replaced +// by the programmer. +func TestLeftoverTemplates(t *testing.T) { + if err := filepath.Walk("./", checkFile); err != nil { + t.Errorf("%v", err) + } + + // If no files were checked that means something fishy happened. Perhaps the + // test was moved to a different directory? + if filesChecked == 0 { + t.Fatalf("failed to find any files to check while traversing ./") + } } diff --git a/makefile b/makefile index 0a152c4b5..e244162bf 100644 --- a/makefile +++ b/makefile @@ -6,6 +6,8 @@ PARALLELISM := 5 # make integration INT_FLAGS="-overwriteExpected -config custom.config.json" # make integration INT_FLAGS="-fingerprintSummary -lintSummary -fingerprintFilter='^[ea]' -lintFilter='^w_ext_cert_policy_explicit_text_not_utf8' -config small.config.json" # make integration INT_FLAGS="-lintSummary -fingerprintSummary -lintFilter='^e_' -config small.config.json" +# make integration INT_FLAGS="-lintSummary -fingerprintSummary -excludeSources='Mozilla,ETSI_ESI' -config small.config.json" +# make integration INT_FLAGS="-includeSources='Mozilla,ETSI_ESI' -config small.config.json" INT_FLAGS := CMDS = zlint zlint-gtld-update diff --git a/resultset.go b/resultset.go new file mode 100644 index 000000000..1e74b39e1 --- /dev/null +++ b/resultset.go @@ -0,0 +1,58 @@ +/* + * ZLint Copyright 2020 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package zlint + +import ( + "github.com/zmap/zcrypto/x509" + "github.com/zmap/zlint/lint" +) + +// ResultSet contains the output of running all lints in a registry against +// a single certificate. +type ResultSet struct { + Version int64 `json:"version"` + Timestamp int64 `json:"timestamp"` + Results map[string]*lint.LintResult `json:"lints"` + NoticesPresent bool `json:"notices_present"` + WarningsPresent bool `json:"warnings_present"` + ErrorsPresent bool `json:"errors_present"` + FatalsPresent bool `json:"fatals_present"` +} + +// Execute lints the given certificate with all of the lints in the provided +// registry. The ResultSet is mutated to trace the lint results obtained from +// linting the certificate. +func (z *ResultSet) execute(cert *x509.Certificate, registry lint.Registry) { + z.Results = make(map[string]*lint.LintResult, len(registry.Names())) + // Run each lints from the registry. + for _, name := range registry.Names() { + res := registry.ByName(name).Execute(cert) + z.Results[name] = res + z.updateErrorStatePresent(res) + } +} + +func (z *ResultSet) updateErrorStatePresent(result *lint.LintResult) { + switch result.Status { + case lint.Notice: + z.NoticesPresent = true + case lint.Warn: + z.WarningsPresent = true + case lint.Error: + z.ErrorsPresent = true + case lint.Fatal: + z.FatalsPresent = true + } +} diff --git a/test/helpers.go b/test/helpers.go index 1c7d567b7..e0611b645 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -44,11 +44,7 @@ func TestLint(lintName string, testCertFilename string) *lint.LintResult { // Important: TestLintCert is only appropriate for unit tests. It will panic if // the lintName is not known or if the lint result is nil. func TestLintCert(lintName string, cert *x509.Certificate) *lint.LintResult { - // NOTE(@cpu): Once `lint.Lints` is not exported this will have to be - // changed, likely to use a function like `lint.LintByName`. For now use the - // exported map directly to consolidate access to this one function instead of - // many individual lint unit tests. - l := lint.Lints[lintName] + l := lint.GlobalRegistry().ByName(lintName) if l == nil { panic(fmt.Sprintf( "Lint name %q does not exist in lint.Lints. "+ diff --git a/zlint.go b/zlint.go index 1bc74f3c8..4f4808cac 100644 --- a/zlint.go +++ b/zlint.go @@ -17,9 +17,6 @@ package zlint import ( - "encoding/json" - "io" - "regexp" "time" "github.com/zmap/zcrypto/x509" @@ -35,74 +32,30 @@ import ( const Version int64 = 3 -// ResultSet contains the output of running all lints against a single certificate. -type ResultSet struct { - Version int64 `json:"version"` - Timestamp int64 `json:"timestamp"` - Results map[string]*lint.LintResult `json:"lints"` - NoticesPresent bool `json:"notices_present"` - WarningsPresent bool `json:"warnings_present"` - ErrorsPresent bool `json:"errors_present"` - FatalsPresent bool `json:"fatals_present"` -} - -func (z *ResultSet) execute(cert *x509.Certificate, filter *regexp.Regexp) { - z.Results = make(map[string]*lint.LintResult, len(lint.Lints)) - for name, l := range lint.Lints { - if filter != nil && !filter.MatchString(name) { - continue - } - res := l.Execute(cert) - z.Results[name] = res - z.updateErrorStatePresent(res) - } -} - -func (z *ResultSet) updateErrorStatePresent(result *lint.LintResult) { - switch result.Status { - case lint.Notice: - z.NoticesPresent = true - case lint.Warn: - z.WarningsPresent = true - case lint.Error: - z.ErrorsPresent = true - case lint.Fatal: - z.FatalsPresent = true - } -} - -// EncodeLintDescriptionsToJSON outputs a description of each lint as JSON -// object, one object per line. -func EncodeLintDescriptionsToJSON(w io.Writer) { - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - for _, lint := range lint.Lints { - _ = enc.Encode(lint) - } -} - -// LintCertificate runs all registered lints on c, producing a ZLint. +// LintCertificate runs all registered lints on c using default options, +// producing a ResultSet. +// +// Using LintCertificate(c) is equivalent to calling LintCertificateEx(c, nil). func LintCertificate(c *x509.Certificate) *ResultSet { - // Instead of panicing on nil certificate, just returns nil and let the client - // panic when accessing ZLint, if they're into panicing. - if c == nil { - return nil - } - - // Run all tests - return LintCertificateFiltered(c, nil) + // Run all lints from the global registry + return LintCertificateEx(c, nil) } -// LintCertificateFiltered runs all lints with names matching the provided -// regexp on c, producing a ResultSet. -func LintCertificateFiltered(c *x509.Certificate, filter *regexp.Regexp) *ResultSet { +// LintCertificateEx runs lints from the provided registry on c producing +// a ResultSet. Providing an explicit registry allows the caller to filter the +// lints that will be run. (See lint.Registry.Filter()) +// +// If registry is nil then the global registry of all lints is used and this +// function is equivalent to calling LintCertificate(c). +func LintCertificateEx(c *x509.Certificate, registry lint.Registry) *ResultSet { if c == nil { return nil } - - // Run tests with provided filter + if registry == nil { + registry = lint.GlobalRegistry() + } res := new(ResultSet) - res.execute(c, filter) + res.execute(c, registry) res.Version = Version res.Timestamp = time.Now().Unix() return res diff --git a/zlint_test.go b/zlint_test.go index 53a3d7fa4..82d137a07 100644 --- a/zlint_test.go +++ b/zlint_test.go @@ -14,7 +14,7 @@ func TestLintNames(t *testing.T) { "e_", // lints.Error } - for name := range lint.Lints { + for _, name := range lint.GlobalRegistry().Names() { var valid bool for _, prefix := range allowedPrefixes { if strings.HasPrefix(name, prefix) { From 6072e242b7ef900e7662d5285916c4524583a82d Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 29 Jan 2020 17:38:17 -0500 Subject: [PATCH 3/5] lint: add locking to registryImpl. This makes the default registry safe for concurrent access. ZLint does not currently register lints from multiple go routines/threads but we may as well make it safe for the future. --- lint/registration.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lint/registration.go b/lint/registration.go index e5c639091..3be8ad831 100644 --- a/lint/registration.go +++ b/lint/registration.go @@ -22,6 +22,7 @@ import ( "regexp" "sort" "strings" + "sync" ) // FilterOptions is a struct used by Registry.Filter to create a sub registry @@ -91,6 +92,7 @@ type Registry interface { // registryImpl implements the Registry interface to provide a global collection // of Lints that have been registered. type registryImpl struct { + sync.RWMutex // lintsByName is a map of all registered lints by name. lintsByName map[string]*Lint // lintNames is a sorted list of all of the registered lint names. It is @@ -153,7 +155,7 @@ func (r *registryImpl) register(l *Lint, initialize bool) error { if l.Name == "" { return errEmptyName } - if existing := r.lintsByName[l.Name]; existing != nil { + if existing := r.ByName(l.Name); existing != nil { return &errDuplicateName{l.Name} } if initialize { @@ -161,6 +163,8 @@ func (r *registryImpl) register(l *Lint, initialize bool) error { return &errBadInit{l.Name, err} } } + r.Lock() + defer r.Unlock() r.lintNames = append(r.lintNames, l.Name) r.lintsByName[l.Name] = l r.lintsBySource[l.Source] = append(r.lintsBySource[l.Source], l) @@ -171,24 +175,32 @@ func (r *registryImpl) register(l *Lint, initialize bool) error { // ByName returns the Lint previously registered under the given name with // Register, or nil if no matching lint name has been registered. func (r *registryImpl) ByName(name string) *Lint { + r.RLock() + defer r.RUnlock() return r.lintsByName[name] } // Names returns a list of all of the lint names that have been registered // in string sorted order. func (r *registryImpl) Names() []string { + r.RLock() + defer r.RUnlock() return r.lintNames } // BySource returns a list of registered lints that have the same LintSource as // provided (or nil if there were no such lints). func (r *registryImpl) BySource(s LintSource) []*Lint { + r.RLock() + defer r.RUnlock() return r.lintsBySource[s] } // Sources returns a SourceList of registered LintSources. The list is not // sorted but can be sorted by the caller with sort.Sort() if required. func (r *registryImpl) Sources() SourceList { + r.RLock() + defer r.RUnlock() var results SourceList for k := range r.lintsBySource { results = append(results, k) From d4cbcd63f06050192c01adbbd6c0b909bfde0e1f Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 11 Feb 2020 10:15:03 -0500 Subject: [PATCH 4/5] lint: export NewRegistration function. --- lint/registration.go | 9 +++++---- lint/registration_test.go | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lint/registration.go b/lint/registration.go index 3be8ad831..a51f76d81 100644 --- a/lint/registration.go +++ b/lint/registration.go @@ -249,7 +249,7 @@ func (r *registryImpl) Filter(opts FilterOptions) (Registry, error) { return r, nil } - filteredRegistry := newRegistry() + filteredRegistry := NewRegistry() sourceExcludes := sourceListToMap(opts.ExcludeSources) sourceIncludes := sourceListToMap(opts.IncludeSources) @@ -308,8 +308,9 @@ func (r *registryImpl) WriteJSON(w io.Writer) { } } -// newRegistry constructs a registryImpl that can be used to register lints. -func newRegistry() *registryImpl { +// NewRegistry constructs a Registry implementation that can be used to register +// lints. +func NewRegistry() *registryImpl { return ®istryImpl{ lintsByName: make(map[string]*Lint), lintsBySource: make(map[LintSource][]*Lint), @@ -318,7 +319,7 @@ func newRegistry() *registryImpl { // globalRegistry is the Registry used by all loaded lints that call // RegisterLint(). -var globalRegistry *registryImpl = newRegistry() +var globalRegistry *registryImpl = NewRegistry() // RegisterLint must be called once for each lint to be executed. Normally, // RegisterLint is called from the Go init() function of a lint implementation. diff --git a/lint/registration_test.go b/lint/registration_test.go index ec1c5844c..4b36bd19b 100644 --- a/lint/registration_test.go +++ b/lint/registration_test.go @@ -81,7 +81,7 @@ func TestRegister(t *testing.T) { Lint: &mockLint{}, Source: ZLint, } - dupeReg := newRegistry() + dupeReg := NewRegistry() _ = dupeReg.register(egLint, true) badInitErr := errors.New("mock init error") @@ -152,7 +152,7 @@ func TestRegister(t *testing.T) { for _, tc := range testCases { var reg *registryImpl if tc.registry == nil { - reg = newRegistry() + reg = NewRegistry() } else { reg = tc.registry } @@ -190,7 +190,7 @@ func TestRegistryFilter(t *testing.T) { } // Create a registry and add some test lints - registry := newRegistry() + registry := NewRegistry() mustRegister(registry, testLint("e_mp_example1", MozillaRootStorePolicy)) mustRegister(registry, testLint("w_mp_example2", MozillaRootStorePolicy)) From 2a7674fb510a0550af51541de380734f762a0b29 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 11 Feb 2020 10:28:10 -0500 Subject: [PATCH 5/5] cmd/zlint: remove -list-lints-schema flag/code. The ZSchema functionality isn't required anymore. --- cmd/zlint/main.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cmd/zlint/main.go b/cmd/zlint/main.go index c7fde7cc2..385c8bcef 100644 --- a/cmd/zlint/main.go +++ b/cmd/zlint/main.go @@ -35,7 +35,6 @@ import ( var ( // flags listLintsJSON bool - listLintsSchema bool listLintSources bool prettyprint bool format string @@ -51,7 +50,6 @@ var ( // flags func init() { flag.BoolVar(&listLintsJSON, "list-lints-json", false, "Print lints in JSON format, one per line") - flag.BoolVar(&listLintsSchema, "list-lints-schema", false, "Print lints as a ZSchema") flag.BoolVar(&listLintSources, "list-lints-source", false, "Print list of lint sources, one per line") flag.StringVar(&format, "format", "pem", "One of {pem, der, base64}") flag.StringVar(&nameFilter, "nameFilter", "", "Only run lints with a name matching the provided regex. (Can not be used with -includeNames/-excludeNames)") @@ -83,16 +81,6 @@ func main() { return } - if listLintsSchema { - names := registry.Names() - fmt.Printf("Lints = SubRecord({\n") - for _, lintName := range names { - fmt.Printf(" %q:LintBool(),\n", lintName) - } - fmt.Printf("})\n") - return - } - if listLintSources { sources := registry.Sources() sort.Sort(sources)