Skip to content

Commit

Permalink
Adding support for linting profiles (#595)
Browse files Browse the repository at this point in the history
* adding support for linting profiles

* at least tests running

* Update v3/lint/profile.go

Absolutely

Co-authored-by: Daniel McCarney <daniel@binaryparadox.net>

* Update v3/newProfile.sh

* adding godoc to AllProfiles

* util: gtld_map autopull updates for 2022-10-06T19:22:06 UTC

* Trigger GHA

* fixing linter

Co-authored-by: Daniel McCarney <daniel@binaryparadox.net>
Co-authored-by: GitHub <noreply@github.com>
  • Loading branch information
3 people committed Oct 16, 2022
1 parent c627333 commit 6292ca4
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 9 deletions.
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,19 @@ func TestCaCommonNameNotMissing2(t *testing.T) {
}
```

Adding New Profiles
----------------
**Generating Profile Scaffolding.** The scaffolding for a new profiles can be created
by running `./newProfile.sh <profile_name>`.

An example is:

```bash
$ ./newProfile.sh my_new_profile
```

This will generate a new file in the `profiles` directory by the name `profile_my_new_profile.go` for you.

Updating the TLD Map
--------------------

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ Example ZLint CLI usage:
zlint -exampleConfig

echo "Lint mycert.pem using a custom configuration for any configurable lints"
zlint -config configFile.toml mycert.pem
zlint -config configFile.toml mycert.pemr

echo "List available lint profiles. A profile is a pre-defined collection of lints."
zlint -list-profiles

See `zlint -h` for all available command line options.

Expand Down
19 changes: 19 additions & 0 deletions v3/cmd/zlint/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

[AppleRootStorePolicyConfig]

[CABFBaselineRequirementsConfig]

[CABFEVGuidelinesConfig]

[CommunityConfig]

[MozillaRootStorePolicyConfig]

[RFC5280Config]

[RFC5480Config]

[RFC5891Config]

[e_rsa_fermat_factorization]
Rounds = 0
37 changes: 35 additions & 2 deletions v3/cmd/zlint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ import (
"github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/formattedoutput"
"github.com/zmap/zlint/v3/lint"

_ "github.com/zmap/zlint/v3/profiles"
)

var ( // flags
listLintsJSON bool
listLintSources bool
listProfiles bool
summary bool
longSummary bool
prettyprint bool
Expand All @@ -46,6 +49,7 @@ var ( // flags
excludeNames string
includeSources string
excludeSources string
profile string
printVersion bool
config string
exampleConfig bool
Expand All @@ -59,6 +63,7 @@ var ( // flags
func init() {
flag.BoolVar(&listLintsJSON, "list-lints-json", false, "Print lints in JSON format, one per line")
flag.BoolVar(&listLintSources, "list-lints-source", false, "Print list of lint sources, one per line")
flag.BoolVar(&listProfiles, "list-profiles", false, "Print profiles in JSON format, one per line")
flag.BoolVar(&summary, "summary", false, "Prints a short human-readable summary report")
flag.BoolVar(&longSummary, "longSummary", false, "Prints a human-readable summary report with details")
flag.StringVar(&format, "format", "pem", "One of {pem, der, base64}")
Expand All @@ -67,6 +72,7 @@ func init() {
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.StringVar(&profile, "profile", "", "Name of the linting profile to use. Equivalent to enumerating all of the lints in a given profile using includeNames")
flag.BoolVar(&printVersion, "version", false, "Print ZLint version and exit")
flag.StringVar(&config, "config", "", "A path to valid a TOML file that is to service as the configuration for a single run of ZLint")
flag.BoolVar(&exampleConfig, "exampleConfig", false, "Print a complete example of a configuration that is usable via the '-config' flag and exit. All values listed in this example will be set to their default.")
Expand Down Expand Up @@ -118,6 +124,18 @@ func main() {
return
}

if listProfiles {
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
for _, profile := range lint.AllProfiles() {
err = enc.Encode(profile)
if err != nil {
log.Fatalf("a critical error occurred while JSON encoding a profile, %s", err)
}
}
return
}

var inform = strings.ToLower(format)
if flag.NArg() < 1 || flag.Arg(0) == "-" {
doLint(os.Stdin, inform, registry)
Expand Down Expand Up @@ -214,6 +232,7 @@ func trimmedList(raw string) []string {
// setLints returns a filtered registry to use based on the nameFilter,
// includeNames, excludeNames, includeSources, and excludeSources flag values in
// use.
//
//nolint:cyclop
func setLints() (lint.Registry, error) {
configuration, err := lint.NewConfigFromFile(config)
Expand All @@ -222,10 +241,17 @@ func setLints() (lint.Registry, error) {
}
lint.GlobalRegistry().SetConfiguration(configuration)
// If there's no filter options set, use the global registry as-is
if nameFilter == "" && includeNames == "" && excludeNames == "" && includeSources == "" && excludeSources == "" {
anyFilters := func(args ...string) bool {
for _, arg := range args {
if arg != "" {
return true
}
}
return false
}
if !anyFilters(nameFilter, includeNames, excludeNames, includeSources, excludeSources, profile) {
return lint.GlobalRegistry(), nil
}

filterOpts := lint.FilterOptions{}
if nameFilter != "" {
r, err := regexp.Compile(nameFilter)
Expand All @@ -250,6 +276,13 @@ func setLints() (lint.Registry, error) {
if includeNames != "" {
filterOpts.IncludeNames = trimmedList(includeNames)
}
if profile != "" {
p, ok := lint.GetProfile(profile)
if !ok {
return nil, fmt.Errorf("lint profile name does not exist: %v", profile)
}
filterOpts.AddProfile(p)
}

return lint.GlobalRegistry().Filter(filterOpts)
}
59 changes: 59 additions & 0 deletions v3/lint/profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* ZLint Copyright 2021 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

type Profile struct {
// Name is a lowercase underscore-separated string describing what a given
// profile aggregates.
Name string `json:"name"`

// A human-readable description of what the Profile checks. Usually copied
// directly from the CA/B Baseline Requirements, RFC 5280, or other published
// document.
Description string `json:"description,omitempty"`

// The source of the check, e.g. "BRs: 6.1.6" or "RFC 5280: 4.1.2.6".
Citation string `json:"citation,omitempty"`

// Programmatic source of the check, BRs, RFC5280, or ZLint
Source LintSource `json:"source,omitempty"`

// The names of the lints that compromise this profile. These names
// MUST be the exact same found within Lint.Name.
LintNames []string `json:"lints"`
}

var profiles = map[string]Profile{}

// RegisterProfile registered the provided profile into the global profile mapping.
func RegisterProfile(profile Profile) {
profiles[profile.Name] = profile
}

// GetProfile returns the Profile for which the provided name matches Profile.Name.
// If no such Profile exists then the `ok` returns false, else true.
func GetProfile(name string) (profile Profile, ok bool) {
profile, ok = profiles[name]
return profile, ok
}

// AllProfiles returns a slice of all Profiles currently registered globally.
func AllProfiles() []Profile {
p := make([]Profile, 0)
for _, profile := range profiles {
p = append(p, profile)
}
return p
}
21 changes: 15 additions & 6 deletions v3/lint/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,21 @@ type FilterOptions struct {

// 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
func (f FilterOptions) Empty() bool {
return f.NameFilter == nil &&
len(f.IncludeNames) == 0 &&
len(f.ExcludeNames) == 0 &&
len(f.IncludeSources) == 0 &&
len(f.ExcludeSources) == 0
}

// AddProfile takes in a Profile and appends all Profile.LintNames
// into FilterOptions.IncludeNames.
func (f *FilterOptions) AddProfile(profile Profile) {
if f.IncludeNames == nil {
f.IncludeNames = make([]string, 0)
}
f.IncludeNames = append(f.IncludeNames, profile.LintNames...)
}

// Registry is an interface describing a collection of registered lints.
Expand Down
30 changes: 30 additions & 0 deletions v3/newProfile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Script to create new profile from template

USAGE="Usage: $0 <ARG1>
ARG1: file_name"

if [ $# -eq 0 ]; then
echo "No arguments provided..."
echo "$USAGE"
exit 1
fi

if [ ! -d profiles ]
then
echo "Directory 'profiles' does not exist. Can't make new file."
exit 1
fi


if [ -e profiles/profile_$1.go ]
then
echo "File already exists. Can't make new file."
exit 1
fi

PROFILE=$1

sed -e "s/PROFILE/${PROFILE}/" profileTemplate > profiles/profile_${PROFILE}.go

echo "Created file profiles/lint_${PROFILE}.go"
27 changes: 27 additions & 0 deletions v3/profileTemplate
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* ZLint Copyright 2021 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 profiles

import "github.com/zmap/zlint/v3/lint"

func init() {
lint.RegisterProfile(lint.Profile{
Name: "PROFILE",
Description: "Fill this in...",
Citation: "Fill this in...",
Source: lint.UnknownLintSource,
LintNames: []string{},
})
}
72 changes: 72 additions & 0 deletions v3/profiles/profiles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* ZLint Copyright 2021 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 profiles

import (
"io/ioutil"
"testing"

"github.com/zmap/zlint/v3/lint"
_ "github.com/zmap/zlint/v3/lints/apple"
_ "github.com/zmap/zlint/v3/lints/cabf_br"
_ "github.com/zmap/zlint/v3/lints/cabf_ev"
_ "github.com/zmap/zlint/v3/lints/community"
_ "github.com/zmap/zlint/v3/lints/etsi"
_ "github.com/zmap/zlint/v3/lints/mozilla"
_ "github.com/zmap/zlint/v3/lints/rfc"
)

// We would like to make sure that there is a generic test that makes sure
// that all profiles actually refer to registered lints.
func TestLintsInAllProfilesExist(t *testing.T) {
for _, profile := range lint.AllProfiles() {
for _, l := range profile.LintNames {
if lint.GlobalRegistry().ByName(l) == nil {
t.Errorf("Profile '%s' declares lint '%s' which does not exist", profile.Name, l)
}
}
}
}

// In order to run TestLintsInAllProfilesExist we need to import all lint source packages in order
// to run their init functions. This test makes sure that if anyone adds a new
// lint source in the future that we don't miss importing it into this test file.
func TestNotMissingAnyLintSources(t *testing.T) {
expected := map[string]bool{
"apple": true,
"cabf_br": true,
"cabf_ev": true,
"community": true,
"etsi": true,
"mozilla": true,
"rfc": true,
}
dir, err := ioutil.ReadDir("../lints")
if err != nil {
t.Fatal(err)
}
for _, info := range dir {
if !info.IsDir() {
continue
}
if _, ok := expected[info.Name()]; !ok {
t.Errorf("We need to import each lint source in order to ensure that all lint names referred to by "+
"declared profiles actually exist. However, we found the directory lints/%s which is not a lint "+
"source that this test is aware of. Please add the following import to the top if this test file: "+
"_ \"github.com/zmap/zlint/v3/lints/%s\"", info.Name(), info.Name())
}
}

}
5 changes: 5 additions & 0 deletions v3/profiles/todo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package profiles

// This file exists purely to avoid the following error until we have at least one profile
//
// no non-test Go files in /home/runner/work/zlint/zlint/v3/profiles

0 comments on commit 6292ca4

Please sign in to comment.