Project: Checkov (Provisioning / Security & Compliance)
File: Python Custom Policies.md

layout: default published: true title: Python Custom Policies nav_order: 2 Custom Policies created in code (in Python) support checking the state of a resources attributes. A Python-based Custom Policy for Checkov consists of sections for Metadata and Policy Definition. Read also how to . Specify a `name`, `ID`, `relevant resources` and `categories`. | Parameter | Description | Example/Comments | |--|--|| | ``name`` | A new policy's unique purpose. It should ideally specify the positive desired outcome of the policy. | | | ``id`` | A mandatory unique identifier of a policy. Native policies written by Prisma Cloud contributors will follow the following convention: ``CKVproviderTypeserialNumber`` | `CKVAWS9` , `CKVGCP12` | | ``supportedresources`` | Infrastructure objects, as described in the scanned IaC's language. This usually contains one specific resource block. If you support multiple resources, you can use `*` to match any type of entity in that specific domain. | `*` use depends on which check base class you extend; see note below table. `?ws*` will match anything where the second character is a `'w'`, the third is a `'s'` and the fourth is a `'_'`. | | ``categories`` | Categorization of a scan. Usually used to produce compliance reports, pipeline analytics and infrastructure health metrics, etc. | | | ``guideline`` | (Optional) Add extra info to help the user to solve the issue. | This is not needed | Note for Supported Resources Parameter: If you extend `checkov.terraform.checks.resource.baseresourcecheck.BaseResourceCheck`, the check is registered for all Terraform resources. The following example produces a policy that ensures that new RDS services spun-up are encrypted at rest, given a scanned Terraform configuration (). Create a new file in the AWS check directory ``checkov/terraform/checks/resource/aws/RDSEncryption.py``. Import the following: ```python from checkov.common.models.enums import CheckResult, CheckCategories from checkov.terraform.checks.resource.baseresourcecheck import BaseResourceCheck ``` Define the meta entities for this check as described in the table above. ```python class RDSEncryption(BaseResourceCheck): def init(self) -> None: name = "Ensure all data stored in the RDS is securely encrypted at rest" id = "CKVAWS16" supportedresources = ("awsdb_instance",) categories = (CheckCategories.ENCRYPTION,) super().init(name=name, id=id, categories=categories, supportedresources=supportedresources) ``` Define a simple check of the ```awsdbinstance``` resource block to determine if ```awsdbinstance``` is disabled. If it is disabled, that needs to cause a ```CheckResult.FAILED``` to occur. ```python def scanresourceconf(self, conf: dict[str, list[Any]]) -> CheckResult: """ Looks for encryption configuration at awsdbinstance: https://www.terraform.io/docs/providers/aws/d/db_instance.html :param conf: awsdbinstance configuration :return: <CheckResult> """ if 'storage_encrypted' in conf.keys(): key = conf if key: return CheckResult.PASSED return CheckResult.FAILED ``` Note: The `conf` parameter is dependent on the resource type, which was chosen via the `supported_resources` class instance attribute. For example, for the `awsdbinstance` resource, we get the following value: ```python conf = { "end_line":

# internal field "start_line": 3, # internal field "allocated_storage": [5], "enabledcloudwatchlogs_exports": [["postgresql", "upgrade"]], "engine": ["postgres"], "instance_class": ["db.t3.small"], "password": ["postgres"], "username": ["postgres"], "address": "awsdbinstance.postgres", # internal field } ``` which is the internal representation of following Terraform resource block ```hcl resource "awsdbinstance" "postgres" { allocated_storage = 5 engine = "postgres" instance_class = "db.t3.small" password = "postgres" username = "postgres" enabledcloudwatchlogs_exports = ["postgresql", "upgrade"] } ``` If more than one resource type was set for `supportedresources`, then it is possible to retrieve the info via the class instance attribute `self.entitytype`. ```python def scanresourceconf(self, conf: dict[str, list[Any]]) -> CheckResult: if self.entitytype == "awsdb_instance": ... elif self.entitytype == "awsrdsclusterinstance": ... ``` Implement `getevaluatedkeys` to allow the check results report show the specified key. ```python def getevaluatedkeys(self) -> List[str]: return ['storage_encrypted/[0]'] ``` If the evaluated keys are determined dynamically, you can set the evaluated key when scanning the resource configuration: ```python def scanresourceconf(self, conf: dict[str, list[Any]]) -> CheckResult: """ Looks for encryption configuration at awsdbinstance: https://www.terraform.io/docs/providers/aws/d/db_instance.html :param conf: awsdbinstance configuration :return: <CheckResult> """ if 'storage_encrypted' in conf.keys(): key = conf if key: self.evaluatedkeys = ['storageencrypted/[0]'] return CheckResult.PASSED return CheckResult.FAILED ``` You can also add `details` to be printed on the execution report: ```python def scanresourceconf(self, conf: dict[str, list[Any]]) -> CheckResult: """ Looks for encryption configuration at awsdbinstance: https://www.terraform.io/docs/providers/aws/d/db_instance.html :param conf: awsdbinstance configuration :return: <CheckResult> """ if 'storage_encrypted' in conf.keys(): key = conf if key: self.evaluatedkeys = ['storageencrypted/[0]'] return CheckResult.PASSED self.details.append("'storage_encrypted' was not found on the resource configuration") return CheckResult.FAILED ``` Produces the following CLI report: Conclude the policy name and operationalize it with the statement: ```python check = RDSEncryption() ``` Terraform and CloudFormation have two base classes extending `BaseResourceCheck`: BaseResourceValueCheck: This check will pass only if the `inspectedkey` is within the `expectedvalues`. If `getexpectedvalue` is not implemented, the default value is `[True]`. ```python class RDSPubliclyAccessible(BaseResourceValueCheck): def init(self) -> None: name = "Ensure all data stored in RDS is not publicly accessible" id = "CKVAWS17" supported_resources = ("AWS::RDS::DBInstance",) categories = (CheckCategories.NETWORKING,) super().init(name=name, id=id, categories=categories, supportedresources=supportedresources, missingblockresult=CheckResult.PASSED) def getinspectedkey(self) -> str: return 'Properties/PubliclyAccessible' def getexpectedvalues(self) -> list[Any]: return [False] ``` Another option is to use `ANY_VALUE`: ```python def getexpectedvalues(self) -> list[Any]: return [ANY_VALUE] ``` BaseResourceNegativeValueCheck: This check will pass only if the `inspectedkey` is NOT within the `forbiddenvalues`. ```python class NeptuneClusterInstancePublic(BaseResourceNegativeValueCheck): def init(self) -> None: name = "Ensure Neptune Cluster instance is not publicly available" id = "CKVAWS102" supportedresources = ['awsneptuneclusterinstance'] categories = [CheckCategories.GENERAL_SECURITY] super().init(name=name, id=id, categories=categories, supportedresources=supportedresources) def getinspectedkey(self) -> str: return 'publicly_accessible/[0]' def getforbiddenvalues(self) -> List[Any]: return [True] ``` To run a scan with the new policy, use the ```checkov```

```python checkov -d /user/tf ``` Checkov is delivered with a set of built-in policies that check for compliance and security best practices at its core. In addition, Checkov enables you to load additional checks, that give the user the ability to author and execute custom policies. This example uses the following directory structure: ```text main.tf variables.tf outputs.tf ``` The example assumes a unique need to enforce bucket ACL policies only when the tag `Scope=PCI` is present. That being the case, the following bucket definition must trigger a failed check result: ```python resource "awss3bucket" "creditcardsbucket" { region = var.region bucket = local.bucket_name acl = "public-read" force_destroy = true tags = { Scope = "PCI", } } ``` To trigger the failed check result, you need to add a new check to ensure PCI related S3 buckets will stay private. Create a new python folder named `myextrachecks` containing the new check: ```text main.tf variables.tf outputs.tf myextrachecks init.py S3PCIPrivateACL.py ``` a. The first time you setup the custom checks folder, you need to also create a file named `init.py`. ```python from os.path import dirname, basename, isfile, join import glob modules = glob.glob(join(dirname(file), "*.py")) all = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('init.py')] ``` b. Complete the matching logic in `S3PCIPrivateACL.py`: ```python from future import annotations from typing import Any from checkov.terraform.checks.resource.baseresourcecheck import BaseResourceCheck from checkov.common.models.enums import CheckResult, CheckCategories class S3PCIPrivateACL(BaseResourceCheck): def init(self) -> None: name = "Ensure PCI Scope buckets has private ACL (enable public ACL for non-pci buckets)" id = "CKVAWS999" supportedresources = ("awss3_bucket",) categories = (CheckCategories.BACKUPANDRECOVERY,) guideline = "Follow the link to get more info https://docs.prismacloud.io/en/enterprise-edition/policy-reference" super().init(name=name, id=id, categories=categories, supportedresources=supportedresources, guideline=guideline) def scanresourceconf(self, conf: dict[str, list[Any]]) -> CheckResult: """ Looks for ACL configuration at awss3bucket and Tag values: https://www.terraform.io/docs/providers/aws/r/s3_bucket.html :param conf: awss3bucket configuration :return: <CheckResult> """ tags = conf.get("tags") if tags and isinstance(tags, list): tags = tags[0] if tags.get("Scope") == "PCI": acl_block = conf['acl'] if acl_block in [["public-read"], ["public-read-write"], ["website"]]: return CheckResult.FAILED return CheckResult.PASSED check = S3PCIPrivateACL() ``` With the new custom check in place, run Checkov: ```python pip install checkov checkov -d . --external-checks-dir myextrachecks ``` Verify the results: ```shell Check: "Ensure PCI Scope buckets has private ACL (enable public ACL for non-pci buckets)" FAILED for resource: awss3bucket.creditcardsbucket File: /main.tf:80-90 Guide: Follow the link to get more info https://docs.prismacloud.io/en/enterprise-edition/policy-reference 80 | resource "awss3bucket" "creditcardsbucket" { 81 | region = var.region 82 | bucket = local.bucket_name 83 | acl = "public-read" 84 | force_destroy = true 85 | 86 | tags = { 87 | Scope = "PCI", 88 | 89 | } 90 | } ``` Attention: Policies cannot share the same file name. If two policies with the same file name exist, only the first one will be loaded.