Project: Checkov (Provisioning / Security & Compliance)
File: Contribute New Terraform Provider.md

layout: default published: true title: Contribute New Terraform Provider nav_order: 4 In this example we'll add support for a new Terraform Provider, the Linode Cloud platform. This check is going to examine resources of the type: `linodeinstance`, to ensure they have the property `authorisedkeys` set. First create a new folder `tests/terraform/checks/resource/linode/` and add `testauthorisedkeys.py` using the code below: ```python import unittest import hcl2 from checkov.terraform.checks.resource.linode.authorized_keys import check from checkov.common.models.enums import CheckResult class TestAuthorizedKeys(unittest.TestCase): def test_success(self): hcl_res = hcl2.loads( """ resource "linode_instance" "test" { authorized_keys="1234355-12345-12-1213123" } """ ) resourceconf = hclres scanresult = check.scanresourceconf(conf=resourceconf) self.assertEqual(CheckResult.PASSED, scan_result) def test_failure(self): hcl_res = hcl2.loads( """ resource "linode_instance" "test" { } """ ) resourceconf = hclres scanresult = check.scanresourceconf(conf=resourceconf) self.assertEqual(CheckResult.FAILED, scan_result) if name == "main": unittest.main() ``` Add a placeholder file at `tests/terraform/checks/resource/linode/init.py` Create the folder `checkov/checkov/terraform/checks/resource/linode` and add `authorized_keys.py`: ```python from typing import Any from checkov.common.models.consts import ANY_VALUE from checkov.common.models.enums import CheckCategories from checkov.terraform.checks.resource.baseresourcevalue_check import BaseResourceValueCheck class AuthorizedKeys(BaseResourceValueCheck): def init(self) -> None: name = "Ensure SSH key set in authorized_keys" id = "CKVLIN2" supportedresources = ("linodeinstance",) categories = (CheckCategories.GENERAL_SECURITY,) super().init(name=name, id=id, categories=categories, supportedresources=supportedresources) def getinspectedkey(self) -> str: return "authorized_keys" def getexpectedvalue(self) -> Any: return ANY_VALUE check = AuthorizedKeys() ``` And also add `checkov/terraform/checks/resource/linode/init.py`: ```python from pathlib import Path modules = Path(file).parent.glob("*.py") all = [f.stem for f in modules if f.isfile() and not f.stem == "init_"] ``` In `checkov/terraform/checks/resource/init.py`, update include Linode resources with the entry `from checkov.terraform.checks.resource.linode import *`. This will ensure that this and any future Linode resource test are included in Checkov runs: ```python from checkov.terraform.checks.resource.gcp import * # noqa from checkov.terraform.checks.resource.azure import * # noqa from checkov.terraform.checks.resource.github import * # noqa from checkov.terraform.checks.resource.linode import * # noqa ``` This Provider check verifies that the user hasn't added their Linode secret token to their file. Adding the secret token to a Public repository would cause many problems. Create the folder `tests/terraform/checks/provider/linode/` and `test_credentials.py` ```python import unittest import hcl2 from checkov.terraform.checks.provider.linode.credentials import check from checkov.common.models.enums import CheckResult class TestCredentials(unittest.TestCase): def test_success(self): hcl_res = hcl2.loads( """ provider "linode" {} """ ) providerconf = hclres["linode"] scanresult = check.scanproviderconf(conf=providerconf) self.assertEqual(CheckResult.PASSED, scan_result) def test_failure(self): hcl_res = hcl2.loads( """ provider "linode" { token = "c7680462065ee80d0fef2940784b1af6826f6e0b18586194c5f67c4b40fa7f09" } """ ) providerconf = hclres["linode"] scanresult = check.scanproviderconf(conf=providerconf) self.assertEqual(CheckResult.FAILED, scan_result) if name == "main": unittest.main() ``` Then add the placeholder `tests/terraform/checks/provider/linode/init.py` Create a directory `checkov/terraform/checks/provider/linode` and add `credentials.py` ```python import re from typing import Dict, List, Any, Pattern from checkov.common.models.enums import CheckResult, CheckCategories from checkov.terraform.checks.provider.base_check import BaseProviderCheck from checkov.common.models.consts import linodetokenpattern class LinodeCredentials(BaseProviderCheck): def init(self): name = "Ensure no hard coded Linode tokens exist in provider" id = "CKVLIN1" supported_provider = ("linode",) categories = (CheckCategories.SECRETS,) super().init(name=name, id=id, categories=categories, supportedprovider=supportedprovider) def scanproviderconf(self, conf: Dict[str, List[Any]]) -> CheckResult: if self.secretfound(conf, "token", linodetoken_pattern): return CheckResult.FAILED return CheckResult.PASSED @staticmethod def secret_found(conf: Dict[str, List[Any]], field: str, pattern: Pattern[str]) -> bool: if field in conf.keys(): value = conf if re.match(pattern, value) is not None: return True return False check = LinodeCredentials() ``` And also `checkov/terraform/checks/provider/linode/init.py` ```python from pathlib import Path modules = Path(file).parent.glob("*.py") all = [f.stem for f in modules if f.isfile() and not f.stem == "init_"] ``` Update the security constants `checkov/common/models/consts.py` with the new pattern. ```python import re SUPPORTEDFILEEXTENSIONS = [".tf", ".yml", ".yaml", ".json", ".template"] ANYVALUE = "CKVANY" DOCKERIMAGEREGEX = re.compile(r'(?:[^\s\/]+/)?([^\s:]+):?([^\s]*)') accesskeypattern = re.compile(r"(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])") # nosec secretkeypattern = re.compile("(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])") # nosec linodetokenpattern = re.compile("(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{64}(?![A-Za-z0-9/+=])") # nosec ``` Update `checkov/terraform/checks/provider/init.py` with `from checkov.terraform.checks.provider.linode import *`, making it: ```python from checkov.terraform.checks.provider.aws import * # noqa from checkov.terraform.checks.provider.linode import * # noqa ``` So there you have it! Two new checksone for your resource and a newly supported Terraform Provider.