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

zlint: refactor lint reg., allow filtering lints used. #372

Merged
merged 9 commits into from
Feb 11, 2020

Conversation

cpu
Copy link
Member

@cpu cpu commented Jan 23, 2020

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 same regex filtering can be done by pre-filtering the provided registry (See notes below).

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. Edit: I decided it made sense to add locking to future proof the implementation for thread safety, see 6072e24 The implementation in this branch is now safe for concurrent access/registration

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 four 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.

Resolves #344

Daniel added 3 commits January 19, 2020 15:20
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.
@cpu cpu requested a review from zakird January 23, 2020 16:06
Daniel added 2 commits January 29, 2020 12:04
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.
@cpu
Copy link
Member Author

cpu commented Jan 30, 2020

Based on some out of band feedback I'm going to try and restructure the Registry into a Linter type and remove the zlint.LintCertificate / zlint.LintCertificateEx package functions, instead defining the lint operation as a function on the linter type, which can be constructed similar to a registry in this branch. Will hopefully push another commit in the next week. If folks have additional feedback it may be best to wait until after that lands so we're all on the same page.

@cardonator
Copy link
Contributor

Thanks for all your hard work on this, @cpu. It's looking really good so far.

With the large amount of changes in this PR, would it also be possible to add a flag that tells LintCertificate to only return lints that met some condition? I.E. in my case, I only care about lints that had a status that wasn't NA or Pass. It would be nice if LintCertificate could be instructed to only return those lints and not every lint that ran. This would be particularly helpful when using the CLI as the output of the tool is pretty hard to parse within a terminal.

@cpu
Copy link
Member Author

cpu commented Jan 30, 2020

Thanks for all your hard work on this, @cpu. It's looking really good so far.

Thanks for taking a look!

would it also be possible to add a flag that tells LintCertificate to only return lints that met some condition?

@cardonator That seems like a reasonable request. I had to implement this sort of filtering in the CFSSL ZLint integration: https://github.com/cloudflare/cfssl/blob/644917271238216c94866f021e2b24ce54555848/signer/local/local.go#L183-L185

I'll see about rolling that in to this branch (or will file a follow-up issue if I don't get to it).

@cpu
Copy link
Member Author

cpu commented Feb 10, 2020

Based on some out of band feedback I'm going to try and restructure the Registry into a Linter type and remove the zlint.LintCertificate / zlint.LintCertificateEx package functions, instead defining the lint operation as a function on the linter type, which can be constructed similar to a registry in this branch.

I spent some time fiddling with this (WIP/broken code here: https://github.com/cpu/zlint/tree/cpu-refactors-lint-registration-again). My experience is that the idea doesn't play nicely with the way lint registration is handled, and the split between the zlint and lint packages. Ultimately I couldn't get something working that wasn't a big re-architecture and worked well for consumers. The linked branch comes close but since consumers don't need to import the zlint package in that design (e.g. see cmd/zlint/main.go) no lints are actually registered.

I'm still happy with the design in this PR w/ the Registry type. It strikes me as a good compromise between having the features we want and maintaining the existing approach to registering lints and the existing subpackages. I vote we move forward with this branch. If folks disagree I'd be happy to close this PR and let someone else take a crack at a proposed redesign.

Copy link
Member

@dadrian dadrian left a comment

Choose a reason for hiding this comment

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

It looks like TravisCI didn't pass?

cmd/zlint/main.go Outdated Show resolved Hide resolved
lint/registration.go Outdated Show resolved Hide resolved
@dadrian
Copy link
Member

dadrian commented Feb 11, 2020

Overall, I think this is really good @cpu. If we can clear up CI, I'm happy to merge.

Daniel added 2 commits February 11, 2020 10:15
@cpu
Copy link
Member Author

cpu commented Feb 11, 2020

If we can clear up CI, I'm happy to merge.

@dadrian I think the CI failure was unrelated to the code. Yesterday I accidentally pushed my branch to the zmap repo first (this PR is from my fork). When I deleted the zmap copy of the branch I think it confused Travis:

$ git clone --depth=50 --branch=cpu-refactors-lint-registration https://github.com/zmap/zlint.git zmap/zlint
Cloning into 'zmap/zlint'...
warning: Could not find remote branch cpu-refactors-lint-registration to clone.
fatal: Remote branch cpu-refactors-lint-registration not found in upstream origin

It looks like CI is happy again as of d4cbcd6.

@cpu
Copy link
Member Author

cpu commented Feb 11, 2020

I'm happy to merge.

One other note: can I request this gets squash merged with the PR desc as the commit message? I think that will be the cleanest. Alternatively I can rebase and squash to one commit before merging but I think it's easier to let Github do the work.

@dadrian dadrian merged commit 7741587 into zmap:master Feb 11, 2020
@cpu cpu deleted the cpu-refactors-lint-registration branch February 11, 2020 18:53
@cpu
Copy link
Member Author

cpu commented Feb 11, 2020

I'll see about rolling that in to this branch (or will file a follow-up issue if I don't get to it).

@cardonator Unfortunately I had to punt lint result filtering to a separate issue for now: #384 I'll try to come back around at that after a RC and reviewing the other open PRs.

@cardonator
Copy link
Contributor

It's all good, @cpu! Thanks for all your work on these large refactorings!

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.

Support configurable lint sources for LintCertificate
3 participants