-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(terraform): Update CKV_AWS_358, add CKV_GCP_125 and CKV_AZURE_24…
…9 for OIDC claims analysis for GitHub (#6960) * feat(terraform): improve CKV_AWS_358, add CKV_GCP_125 and CKV_AZURE_249 Introducing new OIDC checks for TF for GCP(CKV_GCP_125), Azure(CKV_AZURE_249) and AWS(CKV_AWS_358). * fix: allow granular pick of HCL attributes to evaulate w.r.t the resource type This commit introduces a change to the variable rendering logic where one can choose which attributes to ignore with respect to the passed resource type * ignore: added test residuals to gitignore
- Loading branch information
1 parent
4e14082
commit 1bfcf06
Showing
12 changed files
with
666 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import re | ||
|
||
gh_repo_regex = re.compile(r"[\w]+/.+") | ||
gh_abusable_claims = ["workflow", "environment", "ref", "context", "head_ref", "base_ref"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
checkov/terraform/checks/resource/azure/GithubActionsOIDCTrustPolicy.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from typing import Dict, Any, List | ||
from checkov.common.models.enums import CheckCategories, CheckResult | ||
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck | ||
from checkov.common.util.type_forcers import force_list | ||
from checkov.common.util.oidc_utils import gh_abusable_claims, gh_repo_regex | ||
|
||
|
||
class AzureGithubActionsOIDCTrustPolicy(BaseResourceCheck): | ||
def __init__(self) -> None: | ||
name = "Ensure Azure GitHub Actions OIDC trust policy is configured securely" | ||
id = "CKV_AZURE_249" | ||
supported_resources = [ | ||
"azuread_application_federated_identity_credential", | ||
"azuread_application", # Added to support both approaches | ||
] | ||
categories = (CheckCategories.IAM,) | ||
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources) | ||
|
||
def validate_subject_claim(self, subject: str) -> bool: | ||
"""Validates the subject claim for security concerns""" | ||
if not subject: | ||
return False | ||
|
||
# If no colons - invalid format for GitHub Actions claims | ||
if ":" not in subject: | ||
return False | ||
|
||
claim_parts = subject.split(":") | ||
|
||
# Check for wildcards in critical positions | ||
if claim_parts[0] == "*" or claim_parts[1] == "*": | ||
return False | ||
|
||
# Check for abusable claims | ||
if claim_parts[0] in gh_abusable_claims: | ||
return False | ||
|
||
# Validate repo format if repo claim is used | ||
if claim_parts[0] == "repo": | ||
if not gh_repo_regex.match(claim_parts[1]): | ||
return False | ||
|
||
return True | ||
|
||
def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult: | ||
"""Scans the configuration for Azure GitHub Actions OIDC trust policy""" | ||
try: | ||
isAzureAdResource = conf.get("identity_federation", [None])[0] is not None | ||
condition = force_list( | ||
conf.get("subject", [None]) | ||
if not isAzureAdResource | ||
else conf.get("identity_federation", [None])[0].get("subject", [None])[0] | ||
)[0] | ||
if not condition: | ||
return CheckResult.FAILED | ||
|
||
# We should have colon delimited subject claim | ||
if ":" not in condition or condition == "*": | ||
return CheckResult.FAILED | ||
|
||
# At this point we know we have a colon delimited subject claim, so length should be at least 2 | ||
split_condition = condition.split(":") | ||
|
||
# First check -> wildcards | ||
if "*" == split_condition[0] or "*" == split_condition[1]: | ||
return CheckResult.FAILED | ||
|
||
# Second check -> abusable claims | ||
if split_condition[0] in gh_abusable_claims: | ||
return CheckResult.FAILED | ||
|
||
# Third check -> repo format | ||
if split_condition[0] == "repo" and not gh_repo_regex.match(split_condition[1]): | ||
return CheckResult.FAILED | ||
|
||
return CheckResult.PASSED | ||
|
||
except Exception: | ||
return CheckResult.FAILED | ||
|
||
def get_evaluated_keys(self) -> List[str]: | ||
return ["identity_federation/subject", "subject"] | ||
|
||
|
||
check = AzureGithubActionsOIDCTrustPolicy() |
91 changes: 91 additions & 0 deletions
91
checkov/terraform/checks/resource/gcp/GithubActionsOIDCTrustPolicy.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from typing import Dict, Any, List | ||
from checkov.common.models.enums import CheckCategories, CheckResult | ||
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck | ||
from checkov.common.util.oidc_utils import gh_abusable_claims, gh_repo_regex | ||
import re | ||
|
||
|
||
class GithubActionsOIDCTrustPolicy(BaseResourceCheck): | ||
def __init__(self) -> None: | ||
name = "Ensure GCP GitHub Actions OIDC trust policy is configured securely" | ||
id = "CKV_GCP_125" | ||
supported_resources = ["google_iam_workload_identity_pool_provider"] | ||
categories = (CheckCategories.IAM,) | ||
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources) | ||
|
||
def extract_sub_claim_value(self, condition: str) -> str: | ||
"""Extract the claim value from the condition string.""" | ||
if not condition: | ||
return "" | ||
|
||
# Handle both single and double quotes | ||
claim_match = re.search(r"assertion\.sub\s*==\s*['\"]([^'\"]+)['\"]", condition) | ||
if claim_match: | ||
return claim_match.group(1) | ||
return "" | ||
|
||
def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult: | ||
"""Scans the configuration for GitHub Actions OIDC trust policy""" | ||
try: | ||
# Check issuer URI | ||
issuer_uri = conf.get("issuer_uri", [None])[0] | ||
print(f"Issuer URI: {issuer_uri}") | ||
if not issuer_uri or issuer_uri != "https://token.actions.githubusercontent.com": | ||
print("Failed: Invalid issuer URI") | ||
return CheckResult.FAILED | ||
|
||
# Check attribute mapping | ||
attribute_mapping = conf.get("attribute_mapping") | ||
if not attribute_mapping or not isinstance(attribute_mapping, list) or not attribute_mapping[0]: | ||
return CheckResult.FAILED | ||
attribute_mapping = attribute_mapping[0] | ||
if not attribute_mapping or "google.subject" not in attribute_mapping: | ||
print("Failed: Missing google.subject in attribute mapping") | ||
return CheckResult.FAILED | ||
|
||
# Check attribute condition | ||
attribute_condition = conf.get("attribute_condition", False)[0] | ||
if not attribute_condition: | ||
return CheckResult.FAILED | ||
|
||
# Extract claim value | ||
sub_claim_value = self.extract_sub_claim_value(attribute_condition) | ||
if not sub_claim_value: | ||
print("Failed: Could not extract claim value") | ||
return CheckResult.FAILED | ||
|
||
# If no colons - it means we assert something the value without the claim name, which is invalid when using GitHub Actions OIDC | ||
if ":" not in sub_claim_value: | ||
print("Failed: Invalid claim value") | ||
return CheckResult.FAILED | ||
|
||
# Break by colons; Since we already checked for the presence of colons, we can safely assume that the claim is in the form of claim_name:claim_value | ||
claim_parts = sub_claim_value.split(":") | ||
# Check if the first claim or value are wildcards - if yes, the assertion is checking nothing | ||
if claim_parts[0] == "*" or claim_parts[1] == "*": | ||
print("Failed: Wildcard claims") | ||
return CheckResult.FAILED | ||
|
||
# Check if the first claim is an abusable claim - if yes, the whole assertion can be abused | ||
if claim_parts[0] in gh_abusable_claims: | ||
print("Failed: Abusable claim") | ||
return CheckResult.FAILED | ||
|
||
# Lastly, check for the classic "repo" claim | ||
if claim_parts[0] == "repo": | ||
# Check if the repo claim is in the form of org/repo | ||
if not gh_repo_regex.match(claim_parts[1]): | ||
print("Failed: Invalid repo claim") | ||
return CheckResult.FAILED | ||
|
||
return CheckResult.PASSED | ||
|
||
except Exception as e: | ||
print(f"Failed with exception: {str(e)}") | ||
return CheckResult.FAILED | ||
|
||
def get_evaluated_keys(self) -> List[str]: | ||
return ["attribute_condition", "attribute_mapping", "issuer_uri"] | ||
|
||
|
||
check = GithubActionsOIDCTrustPolicy() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.