Linters are static analysis tools that analyze source code and report problems. The term goes all the way back to Bell Labs in 1978 but the concept is still very important today. In my opinion, linters are a key ingredient of a successful DevSecOps implementation, and yet not enough people are aware of linters, how easy they are to use, and how important to quality and security they are.
Linters can be syntax checkers or more complex tools. “Lint” is a more or less a term used for lightweight, simple static analysis. For example, yamllint checks the syntax of YAML files. At first, this tool may seem to be nice but not really necessary; YAML is pretty easy to read and understand. However, it’s simple to make a mistake in YAML and introduce a hard to discover problem. Take this .gitlab-ci.yml for instance:
--- variables: password: "swordfish" include: - template: Code-Quality.gitlab-ci.yml build: stage: build image: name: python:buster script: - ./build.sh artifacts: paths: - target/beanstalk-app.zip variables: password: "taco" include: - template: SAST.gitlab-ci.yml
This file is valid and GitLab will process it. However, it’s not clear what it actually does – did you spot all the errors? In this case, an unexpected password is specified among other issues. This error may introducing a security vulnerability. And this example is short and relatively easy to manually check – what if the YAML file was much longer?
For more complex languages than YAML, linting is even more important. With more expressive language, errors are easier to introduce (through misunderstanding and typos) and harder to notice. Linters also make code more consistent, understandable, and maintainable. They not only improve security but also reduce cost and improve quality.
For a real world example, I’ve been doing a lot of CloudFormation work lately. It’s easy to accidentally create more open security groups and network access control lists than necessary, to forget to enable encryption, or make other such mistakes. cfn_nag and cfn-lint have caught many errors for me, as well as encouraged me to improve the quality by setting resource descriptions and being explicit about intentions.
Another example is with Java. By using PMD to check for logic, syntax, and convention violation errors, the code can be more likely to work as expected. By using Checkstyle, the code is all consistently formatted, follows the same naming conventions, has required comments, and other such benefits that make the code easy to understand and maintain. And easy to understand and maintain inherently means more secure.
Therefore, always add as many linters as possible and have them run as often as possible. Running linters in the git pre-commit hook is ideal (as then detected errors are never even committed). Running them from the build process (maven, msbuild, make, grunt, gulp, etc) is really important. But ultimately, running them in continuous integration is an absolute requirement. Running them daily or weekly is simply not enough.
A common scenario I’ve seen is that static analysis is only done periodically (once per day or once per week) instead of for every commit (via a commit hook or continuous integration). For example, I’ve seen SonarQube set up to run daily for many projects. The problem with this approach is that errors are reported much later than they’re introduced making them lower priority to fix and harder to fix. If a daily SonarQube scan discovers a security vulnerability, management will triage the issue and perhaps put fixing it on the backlog, then eventually an engineer is tasked with fixing it but before they can do so they have to study and understand the relevant code. A superior approach leading to better efficiency and better security is to perform this scanning for every commit and fail the build if the scan fails – that way, the person who introduced the problem has to immediately fix it. This reduces exposure (as detected issues can never progress in the pipeline) and improves efficiency (as the same person who introduced the issue fixes it immediately without having to re-learn anything).
Here’s how a few linters are run on GitLab CI for the VersionPress on AWS project:
lint_ebextensions: stage: test image: sdesbure/yamllint script: - yamllint -s ./beanstalk/.ebextensions/*.config . lint_dockerfile: stage: test image: hadolint/hadolint:latest-debian script: - hadolint beanstalk/Dockerfile cfn_lint: stage: test image: aztek/cfn-lint script: - cfn-lint target/cloudformation.json validate_cloudformation: only: variables: - $AWS_ACCESS_KEY_ID - $AWS_SECRET_ACCESS_KEY stage: test image: python:latest script: - pip install awscli - aws cloudformation validate-template --template-body file://target/cloudformation.json cfn_nag: stage: test image: name: stelligent/cfn_nag entrypoint: [""] script: - cfn_nag target/cloudformation.json
Note that docker is used to run the linters. This approach allows them to be quickly and easily run, and it’s much easier to maintain than having to manually install each one.
And finally, here are my favorite linters that I use frequently:
- shellcheck is a static analysis tool for shell scripts.
- cfn-lint validates templates against the CloudFormation spec and additional checks. Includes checking valid values for resource properties and best practices.
- cfn-nag tool looks for patterns in CloudFormation templates that may indicate insecure infrastructure.
- yamllint does not only check for syntax validity, but for weirdnesses like key repetition and cosmetic problems such as lines length, trailing spaces, indentation, etc.
- PMD is an extensible cross-language static code analyzer. Easy to use from Java via Maven with the Maven PMD Plugin.
- Checkstyle is a development tool to help programmers write Java code that adheres to a coding standard. Easy to use from Java via Maven with the Maven Checkstyle Plugin.
- Hadolint is a Dockerfile linter which validates inline bash.
- CSSLint is a tool to help point out problems with your CSS code.
- pkgcheck and repoman check Gentoo ebuilds (packages).
- GitLab offers SAST (which is a bit more than the usual lightweight linter)