Skip to content

Commit

Permalink
feat(terraform): Github Actions OIDC trust policy check (#5402)
Browse files Browse the repository at this point in the history
* TestGithubActionsOIDCTrustPolicy

* description

* check ID
  • Loading branch information
tronxd committed Aug 3, 2023
1 parent ae7180f commit fa58b9e
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 0 deletions.
65 changes: 65 additions & 0 deletions checkov/terraform/checks/data/aws/GithubActionsOIDCTrustPolicy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from typing import Dict, List, Any
import re
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.common.util.type_forcers import force_list
from checkov.terraform.checks.data.base_check import BaseDataCheck

gh_repo_regex = re.compile(r'repo:[^/]+/[^/]+')


class GithubActionsOIDCTrustPolicy(BaseDataCheck):
def __init__(self):
name = 'Ensure GitHub Actions OIDC trust policies only allows actions from a specific known organization'
id = "CKV_AWS_358"
supported_data = ("aws_iam_policy_document",)
categories = [CheckCategories.IAM]
super().__init__(name=name, id=id, categories=categories, supported_data=supported_data)

def scan_data_conf(self, conf: Dict[str, List[Any]], entity_type: str) -> CheckResult:
statements = force_list(conf.get('statement'))
for statement in statements:
found_federated_gh_oidc = False
if isinstance(statement, dict):
if statement.get('principals'):
principals = statement['principals']
for principal in force_list(principals):
if 'type' not in principal and 'identifiers' not in principal:
continue
principal_type = principal['type']
principal_identifiers = principal['identifiers']
if isinstance(principal_type, list) and len(
principal_type) and 'Federated' in principal_type and isinstance(principal_identifiers,
list):
for identifier in principal_identifiers:
if isinstance(identifier,
list) and 'oidc-provider/token.actions.githubusercontent.com' in \
identifier[0]:
found_federated_gh_oidc = True
break
if not found_federated_gh_oidc:
return CheckResult.PASSED
if found_federated_gh_oidc and not statement.get('condition'):
return CheckResult.FAILED
found_sub_condition_variable = False
found_sub_condition_value = False
for condition in statement.get('condition'):
condition_variables = condition.get('variable')
condition_values = condition.get('values')
if isinstance(condition_variables, list):
for condition_variable in condition_variables:
if condition_variable == 'token.actions.githubusercontent.com:sub':
found_sub_condition_variable = True
break
for condition_value in condition_values:
if isinstance(condition_value, list) and gh_repo_regex.search(condition_value[0]):
found_sub_condition_value = True
break
if found_sub_condition_value and found_sub_condition_variable:
return CheckResult.PASSED
else:
return CheckResult.FAILED

return CheckResult.PASSED


check = GithubActionsOIDCTrustPolicy()
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# pass1

data "aws_iam_policy_document" "pass1" {
version = "2012-10-17"

statement {
effect = "Allow"
actions = [
"lambda:CreateFunction",
"lambda:CreateEventSourceMapping",
"dynamodb:CreateTable",
]
resources = [
"*",
]
}
}

# pass2

data "aws_iam_policy_document" "pass2" {
version = "2012-10-17"

statement {
effect = "Allow"
action = [
"sts:AssumeRoleWithWebIdentity"
]
principals {
identifiers = ["arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com"]
type = "Federated"
}
condition {
test = "StringLike"
values = ["repo:myOrg/myRepo:*"]
variable = "token.actions.githubusercontent.com:sub"
}

condition {
test = "StringEquals"
values = ["sts.amazonaws.com"]
variable = "token.actions.githubusercontent.com:aud"
}
}
}

# pass 3

data "aws_iam_policy_document" "pass3" {
version = "2012-10-17"

statement {
effect = "Allow"
action = [
"sts:AssumeRoleWithWebIdentity"
]
principals {
identifiers = ["arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com"]
type = "Federated"
}
condition {
test = "StringEquals"
values = ["repo:myOrg/myRepo:ref:refs/heads/MyBranch"]
variable = "token.actions.githubusercontent.com:sub"
}

condition {
test = "StringEquals"
values = ["sts.amazonaws.com"]
variable = "token.actions.githubusercontent.com:aud"
}
}
}

data "aws_iam_policy_document" "fail1" {
version = "2012-10-17"

statement {
effect = "Allow"
action = [
"sts:AssumeRoleWithWebIdentity"
]
principals {
identifiers = ["arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com"]
type = "Federated"
}
}
}

data "aws_iam_policy_document" "fail2" {
version = "2012-10-17"

statement {
effect = "Allow"
action = [
"sts:AssumeRoleWithWebIdentity"
]
principals {
identifiers = ["arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com"]
type = "Federated"
}

condition {
test = "StringEquals"
values = ["invalid"]
variable = "token.actions.githubusercontent.com:sub"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import unittest
from pathlib import Path

from checkov.runner_filter import RunnerFilter
from checkov.terraform.checks.data.aws.GithubActionsOIDCTrustPolicy import check
from checkov.terraform.runner import Runner


class TestGithubActionsOIDCTrustPolicy(unittest.TestCase):
def test(self):
test_files_dir = Path(__file__).parent / "example_GithubActionsOIDCTrustPolicy"

report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()

passing_resources = {
'aws_iam_policy_document.pass1',
"aws_iam_policy_document.pass2",
"aws_iam_policy_document.pass3",

}
failing_resources = {
"aws_iam_policy_document.fail1",
"aws_iam_policy_document.fail2"
}

passed_check_resources = set([c.resource for c in report.passed_checks])
failed_check_resources = set([c.resource for c in report.failed_checks])

self.assertEqual(summary["passed"], len(passing_resources))
self.assertEqual(summary["failed"], len(failing_resources))
self.assertEqual(summary["skipped"], 0)
self.assertEqual(summary["parsing_errors"], 0)

self.assertEqual(passing_resources, passed_check_resources)
self.assertEqual(failing_resources, failed_check_resources)


if __name__ == "__main__":
unittest.main()

0 comments on commit fa58b9e

Please sign in to comment.