Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for linting profiles #595

Merged
merged 12 commits into from
Oct 16, 2022
Merged

Adding support for linting profiles #595

merged 12 commits into from
Oct 16, 2022

Conversation

christopher-henderson
Copy link
Member

@christopher-henderson christopher-henderson commented May 1, 2021

What is a Profile

A profile derives from the conversation in #583 wherein we would like to have curated lists of lints that, collectively, refer to a particular requirement from, say, the CABF.

The solution put forth in this PR is that a profile is essentially an alias for a comma separated list of lints submitted via -includeNames, however with some extra metadata attached (such as where did this profile come from).

Profile Implementation

A profile does not appear to have any behavior of it's own. Rather, it is simply a declaration of lints that are logically grouped together by an external requirement. As such, we can borrow infrastructure from our lints that is similar, however much simpler.

I believe that something as simple as the following should suffice.

package profiles

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

func init() {
	lint.RegisterProfile(lint.Profile{
		Name:        "my_new_profile",
		Description: "In general, CAs should do all the things correctly and not do the wrong things",
		Citation:    "That one post I saw on Reddit said that this list was legit",
		Source:      lint.MyTrustWorthyUncle,
		LintNames: []string{
			"e_ca_common_name_missing",
			"e_ca_key_usage_missing",
		},
	})
}

I'm not sure if profiles should have a CheckApplies? One would think that is inherited from individual lints aggregated within the profile.

Running a Profile

Let's say that the_profile_in_question is comprised of the lints a, b, c, d.

./zlint -profile=the_profile_in_question

Is the equivalent to running

./zlint -includeNames="a,b,c,d"

A new CLI flag has been added that lists each profile as line-delimited JSON (much in the same way one prints out available lints).

$ ./zlint list-profiles
{"name":"whatever","lints":["e_ca_common_name_missing","e_ca_key_usage_missing"]}
{"name":"yooodaw","description":"Fill this in...","citation":"Fill this in...","source":"Unknown","lints":[]}
{"name":"yooodaws","description":"Fill this in...","citation":"Fill this in...","source":"Unknown","lints":[]}

Adding a New Profile

The convenience script newProfile.sh <profile_name> has been added that acts in much the same way as newLint.sh does.

Documentation has been added to CONTRIBUTING.md

(resolves #595 )

@@ -97,6 +97,9 @@ Example ZLint CLI usage:
echo "Lint mycert.pem with all of the lints except for ETSI ESI sourced lints"
zlint -excludeSources=ETSI_ESI mycert.pem

echo "List available lint profiles. A profile is a pre-defined collection of lints."
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was apprehensive of adding an example of running a profile here because don't actually have any yet. That is, I didn't want to give an example that doesn't actually run.

@@ -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"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to trigger the init functions in /profiles so that we can build our FilterOptions appropriately. If we try to add this import into lint then it causes an import cycle between lint and profiles 😕

@@ -192,10 +207,17 @@ func trimmedList(raw string) []string {
// 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 == "" {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a style change because this boolean condition was getting long.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, looks good :-)

@@ -0,0 +1,5 @@
package profiles
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sorry, if you have a package with nothing in it but test files then golangci-lint vomits uncontrollably. We'll have to delete this once our first profile lands.

@cpu cpu self-assigned this May 10, 2021
Copy link
Member

@cpu cpu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @christopher-henderson. This seems like a nice extension. I left a few small comments.

Do you have thoughts on the first profile to include? There are a couple points in the PR where you work around not having a profile (e.g. w/ todo.go, and not including a README example). I also had a question about whether we needed a couple model fields. I think maybe we could address both of these things by including a commit in this branch that introduces a profile if there's a good first candidate. What do you think?

v3/lint/profile.go Outdated Show resolved Hide resolved
Comment on lines +27 to +31
// 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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether it's acceptable to consider a Profile's citations/sources as the aggregate of the citations/sources of the lints indicated in LintNames. If so maybe we can simplify the model a little bit and drop these two fields.

If you think there is a use-case for having profile-level citations/sources I think the comments need to be adjusted slightly. "source of the check" reads like there's a singular "check" which (IMO) conflicts with the multiple LintNames.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Sorry, this is likely an artifact that I hadn't bothered to aggregate the discussion in #583 into #594)

So these profiles aren't arbitrary groupings. The plan is that they do come a from source requirement (the first impetus of which is sleevi/cabforum-docs#36). So the idea is that a parent source lists a collection of requirements with an explanation as to why they are grouped.

CABF/BR x.y.z

Section A:
    Due to whatever reason <requirement A>, <requirement B>, <requirement C> from RFRC5280 should...
{
   Citation: "Due to whatever reason, these requirements from RFC5280 should...",
   Source: lint.CABFBR_X_Y_Y
}

"source of the check" reads like there's a singular "check"

Effectively there is, in that if one sub-check fails then the whole check fails, and thus you have failed the profile from the cited source. A bit like how a boolean expression can comprised of multiple boolean expressions (and named).

// In Chris' humble opinion the following is what is considered
// altogether a nice day.
func isANiceDay(sunny, cool, butNotTooCool, lightbreeze, bool) {
    return sunny && cool && butNotTooCool && lightbreeze
}

Each individual one may-or-may-not be desirable in their own context. However, grouped together they answer the single question, "By Chris' definition is this a nice day?"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

....although that was my take on it. Since @sleevi is the first source of these, does it make sense for there to be a simple inheritance type scheme where we can just say that a profile's citations is the list of its lints' citations?

return profile, ok
}

func AllProfiles() []Profile {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can you add a godoc comment on this exported func too? 🙇

v3/newProfile.sh Outdated Show resolved Hide resolved
christopher-henderson and others added 2 commits May 15, 2021 10:08
Absolutely

Co-authored-by: Daniel McCarney <daniel@binaryparadox.net>
@christopher-henderson
Copy link
Member Author

@cpu

Do you have thoughts on the first profile to include?

The first candidates come from @sleevi's work at sleevi/cabforum-docs#36.

  1. Keep this branch around until concrete profiles are published.
    • It would be nice to have something ready on day zero, not entirely necessary from what I can see.
  2. Merge empty and add profiles once they're published.
    • Awkward, like you have pointed out. Plus the framework may need tweeks once we have something concrete to work with

I'm somewhat partial to #1 as I think it makes sense to have code ready to go, but not necessarily have dead code in master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants