Project: Checkov (Provisioning / Security & Compliance)
File: Contribution New IaC Runner.md

Reference PR for the base YAML runner Reference PR for GitHub Action runner (based on the YAML runner) Checkov scans cloud infrastructure configurations to find misconfigurations before they&#39;re deployed. Checkov uses a common command-line interface to manage and analyze infrastructure as code (IaC) scan results across platforms such as Terraform, CloudFormation, Kubernetes, Helm, ARM Templates and Serverless framework. A runner is a unit of code that plugs into the core Checkov &quot;engine&quot; designed to handle the parsing and idiosyncratic formatting of an IaC language such that it can be translated into a set of &quot;definitions&quot;. From there, contributors can create &quot;checks&quot; against the language for best practice and security misconfigurations. IaC : This can be purpose-built, declarative languages for Infrastructure as Code for example Terraform or Cloud Formation. They can also be IaC formats like YAML or JSON which can be uniquely interpreted into a stateful format like Kubernetes (YAML or JSON) or GitHub Actions (YAML). For new IaC formats, In the latter case, a generic JSON or YAML runner already exists to build upon. Registry : A registry is a data structure that collects a group of code objects. There is a registry for &quot;Checks&quot; and a Registry for &quot;Runners&quot; themselves. These allow for Checkov to be extensible. Adding new Checks and new Runners (like we doing here) can be done independently provided the new code is &quot;registered&quot; correctly. Runner Registry : A specific pre-existing registry that tracks all runners Checkov has available. Resource : A single unit within an IaC file upon which a check can be run. A resource may contain other subresource types. An IaC file can have multiple resources within against which Checks can be run. NOTE : There is an advantage to broad resource types which can extend to an entire file. This makes creating checks much easier. The disadvantage is that locations for fixes are far less granular than Each resource type will require it&#39;s own checks registry and checks. Eg. GitHub Actions YAML ```yaml on: pull_request name: unsecure-worfklow jobs: unsecure-job: name: job2 runs-on: ubuntu-latest env: ACTIONSALLOWUNSECURE_COMMANDS: true steps: name: unsecure-step2 run: | echo "goo" secure-job: name: job3 runs-on: ubuntu-latest env: ACTIONSALLOWUNSECURE_COMMANDS: false run: | echo "ok" ``` Above we can define three potential resource types. The &quot;workflow&quot; file as a whole. Misconfigurations (and Fixes) will be presented against the entire file. The &quot;jobs&quot; which we can see is an array Misconfigurations (and potentially fixes) will be presented within a specified line numbers respective to the job. This is a better user experience and workflows. The &quot;steps&quot; within a job which also can be an array. In this case the most accurate result as misconfigurations (and fixes) will be far more precise and can enable the later use of platform features like Smart Fixes. Definition/Entity : This is an internal abstraction of the &quot;resource&quot; IaC into an internal data structure in Checkov against which our Checkov rules will be applied. Check : An autonomous piece of logic that is run against a &quot;resource&quot; within declarative code structure that traverses and understands the idiosyncratic schema and applies a specific best practice or security checks. Report : This is where the results of the checks are summarised into a report prior to being output in a variety of

Like the definitions, the report is initially a data structure that is independent of the eventually requested format (eg. CycloneDX, JSON, CLI text). If your runner is using a known IaC language like JSON or YAML then you will be inheriting a lot from these existing runners. Copy and paste the contents of the &quot;example\runner&quot; which can be found in ../checkov/example\runner changing the name to your new runner type. Eg. cp -r examplerunner mynewiacrunner where mynewiac\_runner will represent your runner name for the rest of these docs You&#39;ll be getting, out of the box, a documented set of files within each should be the instructions of what needs to be modified. The tree structure looks like this mynewiac_runner init.py checks init.py basegithubaction_check.py basegithubactionjobcheck.py job ExampleCheckTrueFalse.py init.py job_registry.py common init.py runner.py Eg. from checkov.mynewiac_runner.checks import * This can be found at the top of the common/output/report.py MYNEWIAC = "mynewiac" Eg. GITHUBACTIONS = "githubactions"; This can be found at the top of the class Runner in mynewiac\_runner/runner.py check_type = CheckType.MYNEWIAC In the example runner there is a checks directory with three key files checks basegithubaction_check.py basegithubactionjobcheck.py job_registry.py Where we have the potential for multiple resource types within a single IaC file (a common occurrence), we have created a base check and from there a sub class of this for the specific resource type. In our example case this is a &quot;job&quot;. Because our resource is a &quot;job&quot; we create a registry for checks to be run against this resource type. You will need to duplicate the base check and registry for each resource type in your IaC. Return to runner.py to make it unique There are a few abstract functions defined in the base ObjectRunner that we need to override. Import\_registry(self): Will register our registries for each block/resource type \parse\file(self.f): Will make a decision as to whether the file &#39;f&#39; is one we care about or not based on unique identity criteria (eg. path, headers, content unique to this IaC). If it is one for us we pass it to the superclass function. get\start\end\lines(self, end, result\config, start): For reporting, different IaC resource blocks will have different methods to determine the beginning and end line numbers. The YAML/JSON runners already handle the &quot;get\start\end\_lines&quot; function for us. Our \parse\file for the github actions runner simply filtered out files that we&#39;re not relevant for our runner Eg. if ".github/workflows/"; in os.path.abspath(f): return super().parsefile(f) Your runner can follow suit and use any number of criteria to ignore files. NOTE: By default, all IaC files are passed into all runners to allow the runners to decided if their registry of checks are appropriate. This is where that decision is made. from checkov.mynewiacrunner.checks.jobregistry import registry as yourrunnerregistry Eg. from checkov.githubactions.checks.jobregistry import registry as githubactionsjobs_registry Add your runner to main.py Make it visible to Checkov by declaring this dependency. You&#39;ll find these starting from line 18 in main.py. Your runner can be added to the bottom of the list like from checkov.mynewiacrunner.runner import Runner as mynewiacrunner From here you can then add the call to the runner to the data structure on line 67 DEFAULT_RUNNERS = ( mynewiac_runner(), cfn_runner(), k8_runner(), sls_runner(), arm_runner(), Add the runner directory name to the build.yaml file &quot;for loop&quot;. .github/workflows/build.yml Line 141 in build.yaml: for i in cloudformation terraform kubernetes serverless arm dockerfile secrets githubconfiguration gitlabconfiguration bitbucketconfiguration mynewiacrunner all Example: Reference: TBD