diff --git a/sdk/rego b/sdk/rego index f71bbd0..87b64c9 160000 --- a/sdk/rego +++ b/sdk/rego @@ -1 +1 @@ -Subproject commit f71bbd0ece9a46f3155509a5b26212b1faa8b948 +Subproject commit 87b64c9464f4d200ee143d0dc84d86e4b263cc63 diff --git a/workflows/cis-benchmark/aws-v1.5.0/efs/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/efs/manifest.yaml index b47c122..c940e74 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/efs/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/efs/manifest.yaml @@ -18,5 +18,10 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include volume-encryption/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.graphql index 2e965a8..af99ed9 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.graphql @@ -8,6 +8,11 @@ query { } name encrypted + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.rego index c35249a..6fa5fdf 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide.rego @@ -7,10 +7,23 @@ decisions[d] { filesystem := account.efs.fileSystems[_] encrypted := filesystem.encrypted - d := shisho.decision.aws.efs.volume_encryption({ - "allowed": encrypted, + "allowed": allow_if_excluded(encrypted, filesystem), "subject": filesystem.metadata.id, "payload": shisho.decision.aws.efs.volume_encryption_payload({"encrypted": encrypted}), }) } + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide_test.rego index 0f00287..14ae693 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/efs/volume-encryption/decide_test.rego @@ -4,7 +4,6 @@ import data.shisho import future.keywords test_whether_encryption_is_enabled_for_aws_efs_file_systems if { - # check if the encryption is enabled for AWS EFS file systems count([d | decisions[d] shisho.decision.is_allowed(d) @@ -23,7 +22,6 @@ test_whether_encryption_is_enabled_for_aws_efs_file_systems if { }, ]}}]}} - # check if all users is accessible for a Google Cloud BigQuery datasets count([d | decisions[d] not shisho.decision.is_allowed(d) @@ -41,4 +39,24 @@ test_whether_encryption_is_enabled_for_aws_efs_file_systems if { "encrypted": true, }, ]}}]}} + + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"efs": {"fileSystems": [ + { + "metadata": {"id": "aws-efs-filesystem|ap-northeast-1|fs-012583c95abf7777c"}, + "encrypted": false, + }, + { + "metadata": {"id": "aws-efs-filesystem|ap-northeast-1|fs-012583c95abf8888c"}, + "encrypted": false, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-efs-filesystem|ap-northeast-1|fs-012583c95abf9999c"}, + "encrypted": true, + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.graphql index 8f587c3..ee9faf6 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.graphql @@ -15,6 +15,11 @@ lastUsedAt } } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.rego index 5a3a9e8..a1cdeba 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide.rego @@ -3,7 +3,7 @@ package policy.aws.iam.credentials_inventory import data.shisho # this policy checks if the credentials are created/used within the last 45 days -# please adjust the `days_of_accepted_age` variable depending on your needs +# please adjust the `must_alert_if_unused_for` variable depending on your needs must_alert_if_unused_for := 45 decisions[d] { @@ -20,7 +20,7 @@ decisions[d] { allowed := active d := shisho.decision.aws.iam.credentials_inventory({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, user), "subject": user.metadata.id, "payload": shisho.decision.aws.iam.credentials_inventory_payload({ "last_used_at": time.format(lat), @@ -72,3 +72,17 @@ used_within_recent_days(ts, d) { # True if the difference is less than `d` days diff_ns < (((1000000000 * 60) * 60) * 24) * d } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide_test.rego index 319c1bb..071e042 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/credentials-inventory/decide_test.rego @@ -5,31 +5,19 @@ import future.keywords now_ns := time.now_ns() -now := time.date(now_ns) +today_string := date_string(now_ns) -today_string := sprintf("%d-%s-%sT00:00:00Z", [now[0], get_month(now[1]), get_day(now[2])]) +two_months_ago_string := date_string(time.add_date(now_ns, 0, -2, 0)) -two_months_ago_ns := time.add_date(now_ns, 0, -2, -0) - -two_months_ago := time.date(two_months_ago_ns) - -two_months_ago_string := sprintf("%d-%s-%sT00:00:00Z", [two_months_ago[0], get_month(two_months_ago[1]), get_day(two_months_ago[2])]) - -get_month(month) = month_string if { - month <= 10 - month_string := sprintf("0%d", [month]) -} else = month_string if { - month > 10 - month_string := sprintf("%d", [month]) +date_string(date_ns) := date_as_string if { + date := time.date(date_ns) + date_as_string := sprintf("%d-%s-%sT00:00:00Z", [date[0], format_digit(date[1]), format_digit(date[2])]) } -get_day(day) := day_string if { - day <= 10 - day_string := sprintf("0%d", [day]) -} else := day_string if { - day > 10 - day_string := sprintf("%d", [day]) -} +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) test_whether_the_user_or_access_key_are_used_within_45_days if { # check if the users are created within 45 days @@ -237,4 +225,32 @@ test_whether_the_user_or_access_key_are_used_within_45_days if { }], }, ]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"iam": {"users": [ + { + "metadata": {"id": "aws-iam-user|AIDA3K53E73AAAAAAAAAA"}, + "createdAt": two_months_ago_string, + "passwordLastUsedAt": null, + "accessKeys": [{ + "createdAt": two_months_ago_string, + "lastUsed": {"lastUsedAt": two_months_ago_string}, + }], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-iam-user|AIDA3K53E73BBBBBBBBBB"}, + "createdAt": two_months_ago_string, + "passwordLastUsedAt": null, + "accessKeys": [{ + "createdAt": two_months_ago_string, + "lastUsed": {"lastUsedAt": two_months_ago_string}, + }], + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.graphql index e13657b..51fb2a4 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.graphql @@ -12,6 +12,11 @@ lastUsedAt } } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.rego index f3a309b..ce20c24 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide.rego @@ -15,7 +15,7 @@ decisions[d] { allowed := count(keys) == 0 d := shisho.decision.aws.iam.key_rotation({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, user), "subject": user.metadata.id, "payload": shisho.decision.aws.iam.key_rotation_payload({ "keys_requiring_rotation": keys, @@ -52,3 +52,17 @@ needs_rotation(key) { t := time.parse_rfc3339_ns(key.lastUsed.lastUsedAt) now - t > (((1000000000 * 60) * 60) * 24) * days_of_accepted_age } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide_test.rego index d11922c..b9cafcf 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/key-rotation/decide_test.rego @@ -5,31 +5,19 @@ import future.keywords now_ns := time.now_ns() -now := time.date(now_ns) +today_string := date_string(now_ns) -today_string := sprintf("%d-%s-%sT00:00:00Z", [now[0], get_month(now[1]), get_day(now[2])]) +four_months_ago_string := date_string(time.add_date(now_ns, 0, -4, 0)) -four_months_ago_ns := time.add_date(now_ns, 0, -4, -0) - -four_months_ago := time.date(four_months_ago_ns) - -four_months_ago_string := sprintf("%d-%s-%sT00:00:00Z", [four_months_ago[0], get_month(four_months_ago[1]), get_day(four_months_ago[2])]) - -get_month(month) = month_string if { - month <= 10 - month_string := sprintf("0%d", [month]) -} else = month_string if { - month > 10 - month_string := sprintf("%d", [month]) +date_string(date_ns) := date_as_string if { + date := time.date(date_ns) + date_as_string := sprintf("%d-%s-%sT00:00:00Z", [date[0], format_digit(date[1]), format_digit(date[2])]) } -get_day(day) := day_string if { - day <= 10 - day_string := sprintf("0%d", [day]) -} else := day_string if { - day > 10 - day_string := sprintf("%d", [day]) -} +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) test_whether_all_access_keys_are_rotated_within_90_days if { # check if all access keys are used or created within 90 days @@ -124,4 +112,44 @@ test_whether_all_access_keys_are_rotated_within_90_days if { ], }, ]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"iam": {"users": [ + { + "metadata": {"id": "aws-iam-user|AIDA3K53E73AAAAAAAAAA"}, + "accessKeys": [ + { + "id": "1", + "createdAt": "2021-03-17T11:49:31Z", + "lastUsed": {"lastUsedAt": four_months_ago_string}, + }, + { + "id": "2", + "createdAt": "2021-03-17T11:49:31Z", + "lastUsed": {"lastUsedAt": today_string}, + }, + ], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-iam-user|AIDA3K53E73BBBBBBBBBB"}, + "accessKeys": [ + { + "id": "1", + "createdAt": four_months_ago_string, + "lastUsed": null, + }, + { + "id": "2", + "createdAt": "2021-03-17T11:49:31Z", + "lastUsed": {"lastUsedAt": today_string}, + }, + ], + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/iam/manifest.yaml index 943f1c2..9e8abe4 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/manifest.yaml @@ -18,6 +18,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include credentials-inventory/decide.graphql - id: key-rotation @@ -30,6 +35,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include key-rotation/decide.graphql - id: password-length @@ -66,6 +76,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include permissive-policy-limitation/decide.graphql - id: root-user-hardware-mfa @@ -81,7 +96,7 @@ jobs: input: schema: !include root-user-hardware-mfa/decide.graphql - id: root-user-key - name: Review the access keys of users + name: Review the access keys of the roor user decide: rego: !include root-user-key/decide.rego with: @@ -93,7 +108,7 @@ jobs: input: schema: !include root-user-key/decide.graphql - id: root-user-mfa - name: Review the usage of MFA devices + name: Review the usage of MFA devices of the roor user decide: rego: !include root-user-mfa/decide.rego with: diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.graphql index 1c6a4c2..f08d544 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.graphql @@ -9,6 +9,11 @@ defaultVersion { rawDocument } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.rego index f2a24b8..835b4ba 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide.rego @@ -10,7 +10,7 @@ decisions[d] { is_permissive := has_administrative_statement(document) d := shisho.decision.aws.iam.administrative_policy_limitation({ - "allowed": is_permissive == false, + "allowed": allow_if_excluded(is_permissive == false, policy), "subject": policy.metadata.id, "payload": shisho.decision.aws.iam.administrative_policy_limitation_payload({}), }) @@ -22,3 +22,17 @@ has_administrative_statement(document) { statement.Action == "*" statement.Resource == "*" } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide_test.rego index 6799082..91f0502 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/permissive-policy-limitation/decide_test.rego @@ -33,4 +33,22 @@ test_whether_the_policies_are_limited_properly if { "defaultVersion": {"rawDocument": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"*\",\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"cloudwatch:*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"autoscaling:*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"iam:CreateServiceLinkedRole\",\n \"Resource\": \"*\",\n \"Condition\": {\n \"StringEquals\": {\n \"iam:AWSServiceName\": [\n \"autoscaling.amazonaws.com\",\n \"ec2scheduled.amazonaws.com\",\n \"elasticloadbalancing.amazonaws.com\",\n \"spot.amazonaws.com\",\n \"spotfleet.amazonaws.com\",\n \"transitgateway.amazonaws.com\"\n ]\n }\n }\n }\n ]\n}"}, }, ]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"iam": {"policies": [ + { + "metadata": {"id": "aws-iam-policy|ANPAI3VAJF5ZCRZ722222"}, + "defaultVersion": {"rawDocument": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"*\",\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"cloudwatch:*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"autoscaling:*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"iam:CreateServiceLinkedRole\",\n \"Resource\": \"*\",\n \"Condition\": {\n \"StringEquals\": {\n \"iam:AWSServiceName\": [\n \"autoscaling.amazonaws.com\",\n \"ec2scheduled.amazonaws.com\",\n \"elasticloadbalancing.amazonaws.com\",\n \"spot.amazonaws.com\",\n \"spotfleet.amazonaws.com\",\n \"transitgateway.amazonaws.com\"\n ]\n }\n }\n }\n ]\n}"}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-iam-policy|ANPAI3VAJF5ZCRZ733333"}, + "defaultVersion": {"rawDocument": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"*\",\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"cloudwatch:*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"autoscaling:*\",\n \"Resource\": \"*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"iam:CreateServiceLinkedRole\",\n \"Resource\": \"*\",\n \"Condition\": {\n \"StringEquals\": {\n \"iam:AWSServiceName\": [\n \"autoscaling.amazonaws.com\",\n \"ec2scheduled.amazonaws.com\",\n \"elasticloadbalancing.amazonaws.com\",\n \"spot.amazonaws.com\",\n \"spotfleet.amazonaws.com\",\n \"transitgateway.amazonaws.com\"\n ]\n }\n }\n }\n ]\n}"}, + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/root-user-usage/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/root-user-usage/decide_test.rego index aacdbb9..69882ea 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/root-user-usage/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/root-user-usage/decide_test.rego @@ -5,31 +5,19 @@ import future.keywords now_ns := time.now_ns() -now := time.date(now_ns) +today_string := date_string(now_ns) -today_string := sprintf("%d-%s-%sT00:00:00Z", [now[0], get_month(now[1]), get_day(now[2])]) +five_months_ago_string := date_string(time.add_date(now_ns, 0, -5, 0)) -five_months_ago_ns := time.add_date(now_ns, 0, -5, -0) - -five_months_ago := time.date(five_months_ago_ns) - -five_months_ago_string := sprintf("%d-%s-%sT00:00:00Z", [five_months_ago[0], get_month(five_months_ago[1]), get_day(five_months_ago[2])]) - -get_month(month) = month_string if { - month <= 10 - month_string := sprintf("0%d", [month]) -} else = month_string if { - month > 10 - month_string := sprintf("%d", [month]) +date_string(date_ns) := date_as_string if { + date := time.date(date_ns) + date_as_string := sprintf("%d-%s-%sT00:00:00Z", [date[0], format_digit(date[1]), format_digit(date[2])]) } -get_day(day) := day_string if { - day <= 10 - day_string := sprintf("0%d", [day]) -} else := day_string if { - day > 10 - day_string := sprintf("%d", [day]) -} +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) test_whether_the_root_user_is_used if { # check if the root user is not used diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.graphql index d1e07fc..6b12248 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.graphql @@ -21,6 +21,11 @@ } } } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.rego index dd1d771..f08821d 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide.rego @@ -15,7 +15,7 @@ decisions[d] { allowed := count(nacls) == 0 d := shisho.decision.aws.networking.acl_ingress({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, vpc), "subject": vpc.metadata.id, "payload": shisho.decision.aws.networking.acl_ingress_payload({"insecure_acls": nacls}), }) @@ -51,3 +51,17 @@ port_range_includes(r, port) { r.from <= port port <= r.to } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide_test.rego index 054f34e..ebc2610 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/acl-ingress/decide_test.rego @@ -4,55 +4,67 @@ import data.shisho import future.keywords test_policy_detects_insecure_nacls if { + acls := [{ + "id": "acl-030f74e1b061fda66", + "entries": [ + { + "cidrBlock": "0.0.0.0/0", + "ipv6CidrBlock": "", + "ruleAction": "ALLOW", + "type": "EGRESS", + "portRange": { + "from": 0, + "to": 0, + }, + }, + { + "cidrBlock": "0.0.0.0/0", + "ipv6CidrBlock": "", + "ruleAction": "DENY", + "type": "EGRESS", + "portRange": { + "from": 0, + "to": 0, + }, + }, + { + "cidrBlock": "0.0.0.0/0", + "ipv6CidrBlock": "", + "ruleAction": "ALLOW", + "type": "INGRESS", + "portRange": { + "from": 0, + "to": 0, + }, + }, + { + "cidrBlock": "0.0.0.0/0", + "ipv6CidrBlock": "", + "ruleAction": "DENY", + "type": "INGRESS", + "portRange": { + "from": 0, + "to": 0, + }, + }, + ], + }] + count([d | decisions[d] not shisho.decision.is_allowed(d) ]) == 1 with input as {"aws": {"accounts": [{"network": {"vpcs": [{ "metadata": {"id": "aws-vpc|ap-northeast-1|vpc-0fb9667dee2b36e00"}, - "acls": [{ - "id": "acl-030f74e1b061fda66", - "entries": [ - { - "cidrBlock": "0.0.0.0/0", - "ipv6CidrBlock": "", - "ruleAction": "ALLOW", - "type": "EGRESS", - "portRange": { - "from": 0, - "to": 0, - }, - }, - { - "cidrBlock": "0.0.0.0/0", - "ipv6CidrBlock": "", - "ruleAction": "DENY", - "type": "EGRESS", - "portRange": { - "from": 0, - "to": 0, - }, - }, - { - "cidrBlock": "0.0.0.0/0", - "ipv6CidrBlock": "", - "ruleAction": "ALLOW", - "type": "INGRESS", - "portRange": { - "from": 0, - "to": 0, - }, - }, - { - "cidrBlock": "0.0.0.0/0", - "ipv6CidrBlock": "", - "ruleAction": "DENY", - "type": "INGRESS", - "portRange": { - "from": 0, - "to": 0, - }, - }, - ], - }], + "acls": acls, + }]}}]}} + + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"network": {"vpcs": [{ + "metadata": {"id": "aws-vpc|ap-northeast-1|vpc-0fb9667dee2b36e00"}, + "acls": acls, + "tags": [{"key": "foo", "value": "bar=piyo"}], }]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/networking/manifest.yaml index a499ee9..091ae6d 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/manifest.yaml @@ -18,6 +18,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include acl-ingress/decide.graphql - id: sg-ingress-v4 @@ -30,6 +35,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include sg-ingress-v4/decide.graphql - id: sg-ingress-v6 @@ -42,5 +52,10 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include sg-ingress-v6/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.graphql index 6d1d06d..baaf372 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.graphql @@ -16,6 +16,11 @@ } } } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.rego index 81ba45a..3229d95 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide.rego @@ -10,7 +10,7 @@ decisions[d] { allowed := has_insecure_rule(g.ipPermissionsIngress) == false d := shisho.decision.aws.networking.sg_ingress_v4({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, g), "subject": g.metadata.id, "payload": shisho.decision.aws.networking.sg_ingress_v4_payload({}), }) @@ -34,3 +34,17 @@ allows(from, to, port) { from == 0 to == 0 } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide_test.rego index a414d2d..26ddda7 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v4/decide_test.rego @@ -51,7 +51,7 @@ test_whether_the_port_ranges_are_configured_properly_for_security_groups if { ]}, ]}}]}} - # check if the port ranges are not configured properly for security groups + # check if the port ranges are not configured properly for security groups count([d | decisions[d] not shisho.decision.is_allowed(d) @@ -97,4 +97,32 @@ test_whether_the_port_ranges_are_configured_properly_for_security_groups if { }, ]}, ]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"network": {"vpcs": [{"securityGroups": [ + { + "metadata": {"id": "aws-vpc-secuirty-group|ap-northeast-1|vpc-0fb9667dee2b77777|sg-06f32151576611111"}, + "name": "default", + "ipPermissionsIngress": [{ + "fromPort": 0, + "toPort": 0, + "ipv4Ranges": [{"cidrIpv4": "0.0.0.0/0"}], + }], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-vpc-secuirty-group|ap-northeast-1|vpc-0fb9667dee2b77777|sg-0c3843ad3c8322222"}, + "name": "test-group-1", + "ipPermissionsIngress": [{ + "fromPort": 22, + "toPort": 22, + "ipv4Ranges": [{"cidrIpv4": "0.0.0.0/0"}], + }], + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.graphql index 6c9f4e0..b775ed5 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.graphql @@ -15,6 +15,11 @@ cidrIpv6 } } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.rego index 22ab3ab..a08ba37 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide.rego @@ -6,11 +6,10 @@ decisions[d] { account := input.aws.accounts[_] vpc := account.network.vpcs[_] g := vpc.securityGroups[_] - allowed := has_insecure_rule(g.ipPermissionsIngress) == false d := shisho.decision.aws.networking.sg_ingress_v6({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, g), "subject": g.metadata.id, "payload": shisho.decision.aws.networking.sg_ingress_v6_payload({}), }) @@ -18,6 +17,7 @@ decisions[d] { has_insecure_rule(rules) { rule := rules[_] + range := rule.ipv6Ranges[_] range.cidrIpv6 == "::/0" @@ -34,3 +34,17 @@ allows(from, to, port) { from == 0 to == 0 } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide_test.rego index 3bcf7b5..9582fcb 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-ingress-v6/decide_test.rego @@ -3,28 +3,32 @@ package policy.aws.networking.sg_ingress_v6 import data.shisho import future.keywords -test_whether_the_default_groups_exist_for_security_groups if { +test_whether_the_port_ranges_are_configured_properly_for_security_groups if { + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"network": {"vpcs": [{"securityGroups": [{ + "metadata": {"id": "aws-vpc-secuirty-group|ap-northeast-1|vpc-0fb9667dee2b36e00|sg-0c3843ad3c833ccf9"}, + "ipPermissionsIngress": [{ + "fromPort": 22, + "toPort": 22, + "ipv4Ranges": [], + "ipv6Ranges": [{"cidrIpv6": "::/0"}], + }], + }]}]}}]}} + count([d | decisions[d] shisho.decision.is_allowed(d) - ]) == 1 with input as {"aws": {"accounts": [{"network": {"vpcs": [{"securityGroups": [ - { - "metadata": {"id": "aws-vpc-secuirty-group|ap-northeast-1|vpc-0fb9667dee2b36e00|sg-0c3843ad3c833ccf9"}, - "ipPermissionsIngress": [{ - "fromPort": 22, - "toPort": 22, - "ipv4Ranges": [], - "ipv6Ranges": [{"cidrIpv6": "::/0"}], - }], - }, - { - "metadata": {"id": "aws-vpc-secuirty-group|ap-northeast-1|vpc-0fb9667dee2b36e00|sg-06f32151576608861"}, - "ipPermissionsIngress": [{ - "fromPort": 0, - "toPort": 0, - "ipv4Ranges": [], - "ipv6Ranges": [], - }], - }, - ]}]}}]}} + ]) == 1 with input as {"aws": {"accounts": [{"network": {"vpcs": [{"securityGroups": [{ + "metadata": {"id": "aws-vpc-secuirty-group|ap-northeast-1|vpc-0fb9667dee2b36e00|sg-0c3843ad3c833ccf9"}, + "ipPermissionsIngress": [{ + "fromPort": 22, + "toPort": 22, + "ipv4Ranges": [], + "ipv6Ranges": [{"cidrIpv6": "::/0"}], + }], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }]}]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.graphql index b251e18..39b92e2 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.graphql @@ -18,6 +18,11 @@ } } } + + tags { + key + value + } } ... on AWSRDSMySQLInstance { metadata { @@ -34,6 +39,11 @@ } } } + + tags { + key + value + } } ... on AWSRDSPostgreSQLInstance { metadata { @@ -50,6 +60,11 @@ } } } + + tags { + key + value + } } ... on AWSRDSSQLServerInstance { metadata { @@ -82,6 +97,11 @@ } } } + + tags { + key + value + } } ... on AWSRDSMariaDBInstance { metadata { @@ -114,6 +134,11 @@ } } } + + tags { + key + value + } } ... on AWSRDSAuroraMySQLInstance { metadata { @@ -130,6 +155,11 @@ } } } + + tags { + key + value + } } ... on AWSRDSAuroraPostgreSQLInstance { metadata { @@ -146,6 +176,11 @@ } } } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.rego index fc10966..84de3f3 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide.rego @@ -9,7 +9,7 @@ decisions[d] { allowed := is_allowed(instance.publiclyAccessible, instance.subnetGroup.vpc.routeTables) == false d := shisho.decision.aws.rds.instance_accessibility({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, instance), "subject": instance.metadata.id, "payload": shisho.decision.aws.rds.instance_accessibility_payload({"is_publicly_accessible": allowed == false}), }) @@ -23,3 +23,17 @@ is_allowed(publicly_accessible, route_tables) { startswith(route.gatewayId, "igw-") route.destinationCidrBlock == "0.0.0.0/0" } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide_test.rego index 7e93cb2..574956e 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-accessibility/decide_test.rego @@ -131,4 +131,54 @@ test_whether_rds_instances_are_publicly_accessible if { ]}}, }, ]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"rds": {"instances": [ + { + "metadata": {"id": "aws-rds-db-instance|ap-northeast-1|test-aurora-1-instance-1"}, + "publiclyAccessible": true, + "subnetGroup": {"vpc": {"routeTables": [ + {"routes": [ + { + "gatewayId": "local", + "destinationCidrBlock": "172.31.0.0/16", + }, + { + "gatewayId": "igw-0493d8c08ca6cb924", + "destinationCidrBlock": "0.0.0.0/0", + }, + ]}, + {"routes": [{ + "gatewayId": "local", + "destinationCidrBlock": "172.31.0.0/16", + }]}, + ]}}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-rds-db-instance|ap-northeast-1|tftestmysql1"}, + "publiclyAccessible": true, + "subnetGroup": {"vpc": {"routeTables": [ + {"routes": [ + { + "gatewayId": "local", + "destinationCidrBlock": "172.31.0.0/16", + }, + { + "gatewayId": "igw-0493d8c08ca6cb924", + "destinationCidrBlock": "0.0.0.0/0", + }, + ]}, + {"routes": [{ + "gatewayId": "local", + "destinationCidrBlock": "172.31.0.0/16", + }]}, + ]}}, + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.graphql index 513be6c..0906d2b 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.graphql @@ -8,18 +8,33 @@ id } autoMinorVersionUpgrade + + tags { + key + value + } } ... on AWSRDSMySQLInstance { metadata { id } autoMinorVersionUpgrade + + tags { + key + value + } } ... on AWSRDSPostgreSQLInstance { metadata { id } autoMinorVersionUpgrade + + tags { + key + value + } } ... on AWSRDSSQLServerInstance { metadata { @@ -32,30 +47,55 @@ id } autoMinorVersionUpgrade + + tags { + key + value + } } ... on AWSRDSMariaDBInstance { metadata { id } autoMinorVersionUpgrade + + tags { + key + value + } } ... on AWSRDSAuroraInstance { metadata { id } autoMinorVersionUpgrade + + tags { + key + value + } } ... on AWSRDSAuroraMySQLInstance { metadata { id } autoMinorVersionUpgrade + + tags { + key + value + } } ... on AWSRDSAuroraPostgreSQLInstance { metadata { id } autoMinorVersionUpgrade + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.rego index 8361d9f..770d66c 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide.rego @@ -9,8 +9,22 @@ decisions[d] { allowed := instance.autoMinorVersionUpgrade d := shisho.decision.aws.rds.instance_auto_upgrade({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, instance), "subject": instance.metadata.id, "payload": shisho.decision.aws.rds.instance_auto_upgrade_payload({"enabled": allowed}), }) } + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide_test.rego index aa32c96..e5005eb 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-auto-upgrade/decide_test.rego @@ -41,4 +41,22 @@ test_whether_auto_minor_version_upgrade_is_enabled_for_rds_instances if { "autoMinorVersionUpgrade": false, }, ]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"rds": {"instances": [ + { + "metadata": {"id": "aws-rds-db-instance|ap-northeast-1|test-aurora-1-instance-1"}, + "autoMinorVersionUpgrade": false, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-rds-db-instance|ap-northeast-1|tftestmysql1"}, + "autoMinorVersionUpgrade": false, + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.graphql index 561bde3..823e58d 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.graphql @@ -8,54 +8,90 @@ id } storageEncrypted + tags { + key + value + } } ... on AWSRDSMySQLInstance { metadata { id } storageEncrypted + tags { + key + value + } } ... on AWSRDSPostgreSQLInstance { metadata { id } storageEncrypted + tags { + key + value + } } ... on AWSRDSSQLServerInstance { metadata { id } storageEncrypted + tags { + key + value + } } ... on AWSRDSOracleInstance { metadata { id } storageEncrypted + tags { + key + value + } } ... on AWSRDSMariaDBInstance { metadata { id } storageEncrypted + tags { + key + value + } } ... on AWSRDSAuroraInstance { metadata { id } storageEncrypted + tags { + key + value + } } ... on AWSRDSAuroraMySQLInstance { metadata { id } storageEncrypted + tags { + key + value + } } ... on AWSRDSAuroraPostgreSQLInstance { metadata { id } storageEncrypted + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.rego index d4ccd2b..db095a1 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide.rego @@ -9,8 +9,22 @@ decisions[d] { allowed := instance.storageEncrypted d := shisho.decision.aws.rds.instance_encryption({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, instance), "subject": instance.metadata.id, "payload": shisho.decision.aws.rds.instance_encryption_payload({"enabled": allowed}), }) } + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide_test.rego index 226bc01..5e404b8 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/instance-encryption/decide_test.rego @@ -41,4 +41,22 @@ test_whether_storage_encryption_is_enabled_for_rds_instances if { "storageEncrypted": false, }, ]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"rds": {"instances": [ + { + "metadata": {"id": "aws-rds-db-instance|ap-northeast-1|test-aurora-1-instance-1"}, + "storageEncrypted": false, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-rds-db-instance|ap-northeast-1|tftestmysql1"}, + "storageEncrypted": false, + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/rds/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/rds/manifest.yaml index f3f1ca3..d0ed85a 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/rds/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/rds/manifest.yaml @@ -18,6 +18,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include instance-accessibility/decide.graphql - id: instance-auto-upgrade @@ -30,6 +35,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include instance-auto-upgrade/decide.graphql - id: instance-encryption @@ -42,5 +52,10 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include instance-encryption/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.graphql index 1822a4a..e8cb722 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.graphql @@ -10,6 +10,11 @@ query { targetBucket targetPrefix } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.rego index 8f16d46..a96ff5e 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide.rego @@ -7,10 +7,23 @@ decisions[d] { bucket := account.s3.buckets[_] allowed := bucket.logging != null - d := shisho.decision.aws.s3.bucket_access_logging({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, bucket), "subject": bucket.metadata.id, "payload": shisho.decision.aws.s3.bucket_access_logging_payload({"enabled": allowed}), }) } + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide_test.rego index 28b0a2f..1b2a705 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-access-logging/decide_test.rego @@ -39,4 +39,21 @@ test_whether_the_access_logging_is_enabled_for_aws_s3_buckets if { "logging": null, }, ]}}]}} + + # Check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"s3": {"buckets": [ + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|shisho-cloud-tfstate-1"}, + "logging": null, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|tf-test-s3-bucket"}, + "logging": null, + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.graphql index a11021f..f786d48 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.graphql @@ -14,6 +14,11 @@ query { keyEnabled } } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.rego index 37134c8..98ca476 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide.rego @@ -7,16 +7,16 @@ decisions[d] { bucket := account.s3.buckets[_] rules := bucket.encryptionConfiguration.rules - allowed := is_allowed(rules) + allowed := includes_accepted_encryption_rule(rules) d := shisho.decision.aws.s3.bucket_encryption({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, bucket), "subject": bucket.metadata.id, "payload": shisho.decision.aws.s3.bucket_encryption_payload({"algorithms": get_algorithms(rules)}), }) } -is_allowed(rules) { +includes_accepted_encryption_rule(rules) { rule := rules[_] rule.keyEnabled == true any([ @@ -25,10 +25,24 @@ is_allowed(rules) { rule.encryptionByDefault.sseAlgorithm == "SSE_KMS", rule.encryptionByDefault.sseAlgorithm == "SSE_S3", ]) -} else = false +} else := false get_algorithms(rules) = x { x := [rule.encryptionByDefault.sseAlgorithm | rule := rules[_] ] } + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide_test.rego index bbd419c..2f62978 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-encryption/decide_test.rego @@ -45,4 +45,28 @@ test_whether_the_bucket_encryption_is_enabled_for_aws_s3_buckets if { }]}, }, ]}}]}} + + # Check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"s3": {"buckets": [ + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-1"}, + "encryptionConfiguration": {"rules": [{ + "encryptionByDefault": {"sseAlgorithm": "AES256"}, + "keyEnabled": false, + }]}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-2"}, + "encryptionConfiguration": {"rules": [{ + "encryptionByDefault": {"sseAlgorithm": "INSECURE_ALGORITHM"}, + "keyEnabled": true, + }]}, + "tags": [{"key": "foo", "value": "diff"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.graphql index 6513246..6a5e2eb 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.graphql @@ -10,6 +10,11 @@ mfaDelete status } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.rego index 4d6180c..c4efb67 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide.rego @@ -7,9 +7,8 @@ decisions[d] { bucket := account.s3.buckets[_] allowed := versioning_needs_mfa(bucket.versioning) - d := shisho.decision.aws.s3.bucket_mfa_delete({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, bucket), "subject": bucket.metadata.id, "payload": shisho.decision.aws.s3.bucket_mfa_delete_payload({"mfa_enabled": allowed}), }) @@ -19,3 +18,17 @@ versioning_needs_mfa(v) { v.status == "ENABLED" v.mfaDelete == "ENABLED" } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide_test.rego index c33f418..0c54157 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-mfa-delete/decide_test.rego @@ -45,4 +45,28 @@ test_whether_the_mfa_delete_is_enabled_for_aws_s3_buckets if { }, }, ]}}]}} + + # Check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"s3": {"buckets": [ + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-1"}, + "versioning": { + "mfaDelete": "ENABLED", + "status": "DISABLED", + }, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-2"}, + "versioning": { + "mfaDelete": "DISABLED", + "status": "ENABLED", + }, + "tags": [{"key": "foo", "value": "invalid"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.graphql index f10840c..5dee24c 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.graphql @@ -12,6 +12,11 @@ ignorePublicAcls restrictPublicBuckets } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.rego index 55d9d4c..9153389 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide.rego @@ -10,7 +10,7 @@ decisions[d] { allowed := blocks_public_access(config) d := shisho.decision.aws.s3.bucket_public_access_block({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, bucket), "subject": bucket.metadata.id, "payload": shisho.decision.aws.s3.bucket_public_access_block_payload({"enabled": allowed}), }) @@ -22,3 +22,17 @@ blocks_public_access(config) { config.ignorePublicAcls == true config.restrictPublicBuckets == true } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide_test.rego index 363482b..090f477 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-public-access/decide_test.rego @@ -53,4 +53,32 @@ test_whether_the_public_access_is_blocked_for_aws_s3_buckets if { }, }, ]}}]}} + + # Check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"s3": {"buckets": [ + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-1"}, + "publicAccessBlockConfiguration": { + "blockPublicAcls": true, + "blockPublicPolicy": false, + "ignorePublicAcls": true, + "restrictPublicBuckets": false, + }, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-2"}, + "publicAccessBlockConfiguration": { + "blockPublicAcls": false, + "blockPublicPolicy": true, + "ignorePublicAcls": false, + "restrictPublicBuckets": true, + }, + "tags": [{"key": "foo", "value": "invalid"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.graphql index 8f5e4dd..335e29a 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.graphql +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.graphql @@ -9,6 +9,11 @@ policy { rawDocument } + + tags { + key + value + } } } } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.rego index 96b7a84..43f508c 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide.rego @@ -6,16 +6,15 @@ decisions[d] { account := input.aws.accounts[_] bucket := account.s3.buckets[_] - allowed := is_allowed(bucket.policy.rawDocument) - + allowed := has_secure_transport_policy(bucket.policy.rawDocument) d := shisho.decision.aws.s3.bucket_transport({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, bucket), "subject": bucket.metadata.id, "payload": shisho.decision.aws.s3.bucket_transport_payload({"is_http_denied": allowed}), }) } -is_allowed(raw_document) { +has_secure_transport_policy(raw_document) { policy_data := json.unmarshal(raw_document) statement := policy_data.Statement[_] @@ -28,3 +27,17 @@ is_allowed(raw_document) { # the key "aws:SecureTransport" might have a string value or an array of string values contains(json.marshal(config_value), "false") } else = false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide_test.rego index 579cb51..d18859b 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide_test.rego +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-transport/decide_test.rego @@ -37,4 +37,27 @@ test_whether_the_bucket_transport_is_configured_properly_for_aws_s3_buckets if { "policy": {"rawDocument": ""}, }, ]}}]}} + + # Check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"s3": {"buckets": [ + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-1"}, + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Id\":\"S3-Console-Auto-Gen-Policy-1679294517777\",\"Statement\":[{\"Sid\":\"S3PolicyStmt-DO-NOT-MODIFY-1679294517777\",\"Effect\":\"Deny\",\"Principal\":{\"Service\":\"logging.s3.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::bucket-test-4/*\",\"Condition\":{\"Bool\":{\"aws:SecureTransport\":[\"true\"]}}}]}"}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-bucket-2"}, + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Deny\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"s3:*\",\"Resource\":[\"arn:aws:s3:::test-bucket-3/*\",\"arn:aws:s3:::test-bucket-1\"]}]}"}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-s3-bucket|ap-northeast-1|test-shisho-bucket-3"}, + "policy": {"rawDocument": ""}, + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/s3/manifest.yaml index 315dcc1..7498323 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/manifest.yaml @@ -18,6 +18,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include bucket-access-logging/decide.graphql - id: bucket-encryption @@ -30,6 +35,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include bucket-encryption/decide.graphql - id: bucket-mfa-delete @@ -42,6 +52,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include bucket-mfa-delete/decide.graphql - id: bucket-public-access @@ -54,6 +69,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include bucket-public-access/decide.graphql - id: bucket-transport @@ -66,5 +86,10 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include bucket-transport/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/manifest.yaml index 64ce3e5..c1e2f4d 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/manifest.yaml @@ -43,4 +43,4 @@ jobs: description: A special list of resource exceptions values: [] input: - schema: !include service-account-project-admin-role/decide.graphql + schema: !include service-account-project-admin-role/decide.graphql \ No newline at end of file diff --git a/workflows/csp/aws-fsbp/cloudfront/logging/decide.graphql b/workflows/csp/aws-fsbp/cloudfront/logging/decide.graphql index 9ee974a..a9a02a7 100644 --- a/workflows/csp/aws-fsbp/cloudfront/logging/decide.graphql +++ b/workflows/csp/aws-fsbp/cloudfront/logging/decide.graphql @@ -12,6 +12,11 @@ query { bucketId } } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/cloudfront/logging/decide.rego b/workflows/csp/aws-fsbp/cloudfront/logging/decide.rego index a22621a..42408a3 100644 --- a/workflows/csp/aws-fsbp/cloudfront/logging/decide.rego +++ b/workflows/csp/aws-fsbp/cloudfront/logging/decide.rego @@ -8,7 +8,7 @@ decisions[d] { allowed := has_logging_bucket(dist.config) d := shisho.decision.aws.cloudfront.logging({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, dist), "subject": dist.metadata.id, "payload": shisho.decision.aws.cloudfront.logging_payload({"bucket_id": logging_bucket_id(dist.config)}), }) @@ -22,3 +22,17 @@ logging_bucket_id(cfg) := cfg.logging.bucketId { cfg.logging != null cfg.logging.bucketId != "" } else := "" + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/csp/aws-fsbp/cloudfront/logging/decide_test.rego b/workflows/csp/aws-fsbp/cloudfront/logging/decide_test.rego index 344fb07..0cae534 100644 --- a/workflows/csp/aws-fsbp/cloudfront/logging/decide_test.rego +++ b/workflows/csp/aws-fsbp/cloudfront/logging/decide_test.rego @@ -28,3 +28,22 @@ test_buckets_with_no_logging_will_be_denied if { }, ]}}]}} } + +test_tag_exception_works if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"cloudFront": {"distributions": [ + { + "metadata": {"id": "aws-cloudfront-distribution|E6M4TFOSOK0BL"}, + "config": {"logging": null}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-cloudfront-distribution|E26UGWQKUHTIWJ"}, + "config": {"logging": null}, + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} +} diff --git a/workflows/csp/aws-fsbp/cloudfront/manifest.yaml b/workflows/csp/aws-fsbp/cloudfront/manifest.yaml index 49cc8ba..8342b20 100644 --- a/workflows/csp/aws-fsbp/cloudfront/manifest.yaml +++ b/workflows/csp/aws-fsbp/cloudfront/manifest.yaml @@ -18,6 +18,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include logging/decide.graphql - id: "origin-transport" @@ -30,6 +35,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include origin-transport/decide.graphql - id: "origin-access-control" @@ -42,6 +52,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include origin-access/decide.graphql - id: "root-object" @@ -54,6 +69,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include root-object/decide.graphql - id: "transport" @@ -66,5 +86,10 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include transport/decide.graphql diff --git a/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.graphql b/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.graphql index 13da24c..899ee08 100644 --- a/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.graphql +++ b/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.graphql @@ -18,6 +18,11 @@ query { } } } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.rego b/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.rego index 2603d00..ecbe083 100644 --- a/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.rego +++ b/workflows/csp/aws-fsbp/cloudfront/origin-access/decide.rego @@ -9,7 +9,7 @@ decisions[d] { origins := s3_backend_origins(dist) allowed := includes_unallowed_origins(origins) == false d := shisho.decision.aws.cloudfront.origin_access_control({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, dist), "subject": dist.metadata.id, "payload": shisho.decision.aws.cloudfront.origin_access_control_payload({"origins": origins}), "severity": custom_severity(origins), @@ -49,3 +49,17 @@ custom_severity_of(o) := shisho.decision.severity_low { o.origin_access_control_configured == "" o.origin_access_identity_configured != "" } else := null + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/csp/aws-fsbp/cloudfront/origin-access/decide_test.rego b/workflows/csp/aws-fsbp/cloudfront/origin-access/decide_test.rego index 4211caf..a3305ad 100644 --- a/workflows/csp/aws-fsbp/cloudfront/origin-access/decide_test.rego +++ b/workflows/csp/aws-fsbp/cloudfront/origin-access/decide_test.rego @@ -95,3 +95,31 @@ test_no_s3_origins_will_be_denied if { }], }]}}]}} } + +test_tag_exception_works if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"cloudFront": {"distributions": [{ + "metadata": {"id": "aws-cloudfront-distribution|E6M4TFOSOK0BL"}, + "origins": [ + { + "id": "test-alb-2-795120637.ap-northeast-1.elb.amazonaws.com", + "domainName": "test-alb-2-795120637.ap-northeast-1.elb.amazonaws.com", + "accessControlId": null, + "backend": {"__typename": "AWSCloudFrontDistributionOriginBackendELBLoadBalancer"}, + }, + { + "id": "test-shisho-bucket-3.s3.ap-northeast-1.amazonaws.com", + "domainName": "test-shisho-bucket-3.s3.ap-northeast-1.amazonaws.com", + "accessControlId": null, + "backend": { + "__typename": "AWSCloudFrontDistributionOriginBackendS3Bucket", + "accessIdentityId": "this-value-is-set", + }, + }, + ], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} +} diff --git a/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.graphql b/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.graphql index 976456f..8844ee9 100644 --- a/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.graphql +++ b/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.graphql @@ -51,6 +51,11 @@ query { targetOriginId viewerProtocolPolicy } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.rego b/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.rego index c713d91..ea77e23 100644 --- a/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.rego +++ b/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide.rego @@ -13,7 +13,7 @@ decisions[d] { origins := origin_transports(dist) allowed := includes_unallowed_origin_transport(dist, origins) == false d := shisho.decision.aws.cloudfront.origin_transport({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, dist), "subject": dist.metadata.id, "payload": shisho.decision.aws.cloudfront.origin_transport_payload({"origins": origins}), }) @@ -48,6 +48,20 @@ includes_unallowed_origin_transport(dist, origins) { viewer_can_use_http(dist) } else := false +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed + ########################### # origin_transport_version ########################### @@ -59,7 +73,7 @@ decisions[d] { origins := origin_transports(dist) allowed := includes_unallowed_origin_transport_version(dist, origins) == false d := shisho.decision.aws.cloudfront.origin_transport_version({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, dist), "subject": dist.metadata.id, "payload": shisho.decision.aws.cloudfront.origin_transport_version_payload({"origins": origins}), }) diff --git a/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide_test.rego b/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide_test.rego index ede99e0..8843658 100644 --- a/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide_test.rego +++ b/workflows/csp/aws-fsbp/cloudfront/origin-transport/decide_test.rego @@ -185,3 +185,29 @@ test_origin_with_match_viewer_dependenet_protocol if { "cacheBehaviors": [], }]}}]}} } + +test_tag_exception_works if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + d.header.kind == shisho.decision.aws.cloudfront.origin_transport_kind + ]) == 1 with input as {"aws": {"accounts": [{"cloudFront": {"distributions": [{ + "metadata": {"id": "aws-cloudfront-distribution|E6MAAAOSOK0BL"}, + "origins": [{ + "id": "test-alb-2-795120637.ap-northeast-1.elb.amazonaws.com", + "domainName": "test-alb-2-795120637.ap-northeast-1.elb.amazonaws.com", + "backend": { + "__typename": "AWSCloudFrontDistributionOriginBackendELBLoadBalancer", + "protocolPolicy": "MATCH_VIEWER", + "sslProtocols": [], + }, + }], + "defaultCacheBehavior": { + "targetOriginId": "test-shisho-bucket-3.s3.ap-northeast-1.amazonaws.com", + "viewerProtocolPolicy": "ALLOW_ALL", + }, + "cacheBehaviors": [], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} +} diff --git a/workflows/csp/aws-fsbp/cloudfront/root-object/decide.graphql b/workflows/csp/aws-fsbp/cloudfront/root-object/decide.graphql index 40977ff..ac3a078 100644 --- a/workflows/csp/aws-fsbp/cloudfront/root-object/decide.graphql +++ b/workflows/csp/aws-fsbp/cloudfront/root-object/decide.graphql @@ -9,6 +9,11 @@ query { config { defaultRootObject } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/cloudfront/root-object/decide.rego b/workflows/csp/aws-fsbp/cloudfront/root-object/decide.rego index 9a5dd99..7404379 100644 --- a/workflows/csp/aws-fsbp/cloudfront/root-object/decide.rego +++ b/workflows/csp/aws-fsbp/cloudfront/root-object/decide.rego @@ -8,7 +8,7 @@ decisions[d] { allowed := has_default_root_object(dist.config) d := shisho.decision.aws.cloudfront.default_root_object({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, dist), "subject": dist.metadata.id, "payload": shisho.decision.aws.cloudfront.default_root_object_payload({"default_root_object": default_root_object(dist.config)}), }) @@ -22,3 +22,17 @@ default_root_object(cfg) := cfg.defaultRootObject { cfg.defaultRootObject != null cfg.defaultRootObject != "" } else := "" + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/csp/aws-fsbp/cloudfront/root-object/decide_test.rego b/workflows/csp/aws-fsbp/cloudfront/root-object/decide_test.rego index 1b71574..b02e6c9 100644 --- a/workflows/csp/aws-fsbp/cloudfront/root-object/decide_test.rego +++ b/workflows/csp/aws-fsbp/cloudfront/root-object/decide_test.rego @@ -34,3 +34,22 @@ test_configured_default_root_object_will_be_allowed if { }, ]}}]}} } + +test_tag_exception_works if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"cloudFront": {"distributions": [ + { + "metadata": {"id": "aws-cloudfront-distribution|E26BBBQKUHTIWJ"}, + "config": {"defaultRootObject": null}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }, + { + "metadata": {"id": "aws-cloudfront-distribution|E26AAAQKUHTIWJ"}, + "config": {"defaultRootObject": null}, + "tags": [{"key": "foo", "value": "unrelated"}], + }, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} +} diff --git a/workflows/csp/aws-fsbp/cloudfront/transport/decide.graphql b/workflows/csp/aws-fsbp/cloudfront/transport/decide.graphql index cbf420f..fabd1e9 100644 --- a/workflows/csp/aws-fsbp/cloudfront/transport/decide.graphql +++ b/workflows/csp/aws-fsbp/cloudfront/transport/decide.graphql @@ -15,6 +15,11 @@ query { targetOriginId viewerProtocolPolicy } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/cloudfront/transport/decide.rego b/workflows/csp/aws-fsbp/cloudfront/transport/decide.rego index eca1f7c..cfac226 100644 --- a/workflows/csp/aws-fsbp/cloudfront/transport/decide.rego +++ b/workflows/csp/aws-fsbp/cloudfront/transport/decide.rego @@ -9,7 +9,7 @@ decisions[d] { behaviors := cache_behaviors(dist) allowed := includes_unallowed_behavior(behaviors) d := shisho.decision.aws.cloudfront.transport({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, dist), "subject": dist.metadata.id, "payload": shisho.decision.aws.cloudfront.transport_payload({"cache_behaviors": behaviors}), }) @@ -37,3 +37,17 @@ includes_unallowed_behavior(behaviors) { b.viewer_protocol_policy != "HTTPS_ONLY" b.viewer_protocol_policy != "REDIRECT_TO_HTTPS" } else := false + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/csp/aws-fsbp/cloudfront/transport/decide_test.rego b/workflows/csp/aws-fsbp/cloudfront/transport/decide_test.rego index 595bed9..177fb11 100644 --- a/workflows/csp/aws-fsbp/cloudfront/transport/decide_test.rego +++ b/workflows/csp/aws-fsbp/cloudfront/transport/decide_test.rego @@ -15,6 +15,21 @@ test_permissive_protocol_policy_will_be_denied if { }, "cacheBehaviors": [], }]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"cloudFront": {"distributions": [{ + "metadata": {"id": "aws-cloudfront-distribution|EAA4TFOSOK0BL"}, + "defaultCacheBehavior": { + "targetOriginId": "test-bucket.s3.ap-northeast-1.amazonaws.com", + "viewerProtocolPolicy": "ALLOW_ALL", + }, + "cacheBehaviors": [], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } test_strict_protocol_policy_will_be_denied if { @@ -41,4 +56,19 @@ test_strict_protocol_policy_will_be_denied if { }, "cacheBehaviors": [], }]}}]}} + + # check tag_exceptions works + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"cloudFront": {"distributions": [{ + "metadata": {"id": "aws-cloudfront-distribution|EAA4TFOSOK0BL"}, + "defaultCacheBehavior": { + "targetOriginId": "test-bucket.s3.ap-northeast-1.amazonaws.com", + "viewerProtocolPolicy": "REDIRECT_TO_HTTPS", + }, + "cacheBehaviors": [], + "tags": [{"key": "foo", "value": "bar=piyo"}], + }]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } diff --git a/workflows/csp/aws-fsbp/ecs/manifest.yaml b/workflows/csp/aws-fsbp/ecs/manifest.yaml index 8cf8b29..76cac62 100644 --- a/workflows/csp/aws-fsbp/ecs/manifest.yaml +++ b/workflows/csp/aws-fsbp/ecs/manifest.yaml @@ -18,6 +18,11 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include service/decide.graphql - id: task @@ -30,5 +35,10 @@ jobs: multiple: true description: A special list of resource exceptions values: [] + tag_exceptions: + type: string + multiple: true + description: A list of AWS tags with which resources can have any settings automatically. For instance, if you include `Environment=production` for this value, all resources with `Environment=production` will be allowed automatically. + values: [] input: schema: !include task/decide.graphql diff --git a/workflows/csp/aws-fsbp/ecs/service/decide.graphql b/workflows/csp/aws-fsbp/ecs/service/decide.graphql index 51bc48e..71ff41c 100644 --- a/workflows/csp/aws-fsbp/ecs/service/decide.graphql +++ b/workflows/csp/aws-fsbp/ecs/service/decide.graphql @@ -26,6 +26,11 @@ query { } } } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/ecs/service/decide.rego b/workflows/csp/aws-fsbp/ecs/service/decide.rego index 7a16a71..8bc9d8d 100644 --- a/workflows/csp/aws-fsbp/ecs/service/decide.rego +++ b/workflows/csp/aws-fsbp/ecs/service/decide.rego @@ -9,7 +9,7 @@ decisions[d] { allowed := service_has_automatic_ip_assignment(service) == false d := shisho.decision.aws.ecs.service_public_ip({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, service), "subject": service.metadata.id, "payload": shisho.decision.aws.ecs.service_public_ip_payload({ "public_ip_assigned": service_public_assignment(service), @@ -55,3 +55,17 @@ service_subnets(s) := [sb.id | sb := s.networkConfiguration.vpcConfiguration.sub s.networkConfiguration != null s.networkConfiguration.vpcConfiguration != null } else := [] + +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed diff --git a/workflows/csp/aws-fsbp/ecs/service/decide_test.rego b/workflows/csp/aws-fsbp/ecs/service/decide_test.rego index 0b6062f..6c794bc 100644 --- a/workflows/csp/aws-fsbp/ecs/service/decide_test.rego +++ b/workflows/csp/aws-fsbp/ecs/service/decide_test.rego @@ -45,6 +45,29 @@ test_service_with_auto_ip_assignment_in_fargate_will_be_denied if { }}, }]}, ]}}]}} + + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"ecs": {"clusters": [ + {"services": []}, + {"services": [{ + "name": "h4b-ecs-service", + "launchType": null, + "capacityProviderStrategy": [{"name": "FARGATE"}], + "metadata": {"id": "aws-ecs-service|ap-northeast-1|h4b-ecs-cluster|h4b-ecs-service"}, + "networkConfiguration": {"vpcConfiguration": { + "subnets": [ + {"id": "subnet-09281c83ac0d0279b"}, + {"id": "subnet-0f581a6ce97387764"}, + ], + "securityGroups": [{"id": "sg-0b6ca13dca0878d10"}], + "assignPublicIp": "ENABLED", + }}, + "tags": [{"key": "foo", "value": "bar=piyo"}], + }]}, + ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } test_service_without_auto_public_ip_assignment_will_be_allowed if { diff --git a/workflows/csp/aws-fsbp/ecs/task/decide.graphql b/workflows/csp/aws-fsbp/ecs/task/decide.graphql index f165196..46ab9fc 100644 --- a/workflows/csp/aws-fsbp/ecs/task/decide.graphql +++ b/workflows/csp/aws-fsbp/ecs/task/decide.graphql @@ -23,6 +23,11 @@ query { } } } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/ecs/task/decide.rego b/workflows/csp/aws-fsbp/ecs/task/decide.rego index 0c6b2d7..097e914 100644 --- a/workflows/csp/aws-fsbp/ecs/task/decide.rego +++ b/workflows/csp/aws-fsbp/ecs/task/decide.rego @@ -10,7 +10,7 @@ decisions[d] { containers := service_container_privilege_configs(service) allowed := has_privileged_containers(containers) == false d := shisho.decision.aws.ecs.container_privilege({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, service), "subject": service.metadata.id, "payload": shisho.decision.aws.ecs.container_privilege_payload({"containers": containers}), }) @@ -28,6 +28,20 @@ has_privileged_containers(containers) { c.privileged } else := false +allow_if_excluded(allowed, r) { + data.params != null + + tag := data.params.tag_exceptions[_] + elements := split(tag, "=") + + tag_key := elements[0] + tag_value := concat("=", array.slice(elements, 1, count(elements))) + + t := r.tags[_] + t.key == tag_key + t.value == tag_value +} else := allowed + decisions[d] { account := input.aws.accounts[_] cluster := account.ecs.clusters[_] @@ -36,7 +50,7 @@ decisions[d] { containers := service_container_filesystem_configs(service) allowed := has_writeable_root_fs(containers) == false d := shisho.decision.aws.ecs.container_fs_permission({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, service), "subject": service.metadata.id, "payload": shisho.decision.aws.ecs.container_fs_permission_payload({"containers": containers}), }) diff --git a/workflows/csp/aws-fsbp/ecs/task/decide_test.rego b/workflows/csp/aws-fsbp/ecs/task/decide_test.rego index f5e47a6..a6ee030 100644 --- a/workflows/csp/aws-fsbp/ecs/task/decide_test.rego +++ b/workflows/csp/aws-fsbp/ecs/task/decide_test.rego @@ -7,6 +7,23 @@ import data.shisho import future.keywords test_task_with_writeable_root_fs_container_denied if { + td := { + "arn": "arn:aws:ecs:ap-northeast-1:779392188153:task-definition/example-ecs-task-definition:1", + "containerDefinitions": [ + { + "__typename": "AWSECSLinuxContainerDefinition", + "name": "apache-helloworld", + "privileged": false, + "readonlyRootFilesystem": false, + }, + { + "__typename": "AWSECSLinuxContainerDefinition", + "name": "apache-helloworld", + "privileged": false, + "readonlyRootFilesystem": true, + }, + ], + } count([d | decisions[d] not shisho.decision.is_allowed(d) @@ -15,25 +32,22 @@ test_task_with_writeable_root_fs_container_denied if { {"services": []}, {"services": [{ "metadata": {"id": "aws-ecs-service|ap-northeast-1|example-ecs-cluster|example-ecs-service"}, - "taskDefinition": { - "arn": "arn:aws:ecs:ap-northeast-1:779392188153:task-definition/example-ecs-task-definition:1", - "containerDefinitions": [ - { - "__typename": "AWSECSLinuxContainerDefinition", - "name": "apache-helloworld", - "privileged": false, - "readonlyRootFilesystem": false, - }, - { - "__typename": "AWSECSLinuxContainerDefinition", - "name": "apache-helloworld", - "privileged": false, - "readonlyRootFilesystem": true, - }, - ], - }, + "taskDefinition": td, + }]}, + ]}}]}} + count([d | + decisions[d] + shisho.decision.is_allowed(d) + d.header.kind == shisho.decision.aws.ecs.container_fs_permission_kind + ]) == 1 with input as {"aws": {"accounts": [{"ecs": {"clusters": [ + {"services": []}, + {"services": [{ + "metadata": {"id": "aws-ecs-service|ap-northeast-1|example-ecs-cluster|example-ecs-service"}, + "taskDefinition": td, + "tags": [{"key": "foo", "value": "bar=piyo"}], }]}, ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } test_task_with_readonly_root_fs_container_allowed if { @@ -67,6 +81,23 @@ test_task_with_readonly_root_fs_container_allowed if { } test_task_with_privileged_container_denied if { + td := { + "arn": "arn:aws:ecs:ap-northeast-1:779392188153:task-definition/example-ecs-task-definition:1", + "containerDefinitions": [ + { + "__typename": "AWSECSLinuxContainerDefinition", + "name": "apache-helloworld", + "privileged": true, + "readonlyRootFilesystem": false, + }, + { + "__typename": "AWSECSLinuxContainerDefinition", + "name": "apache-helloworld", + "privileged": false, + "readonlyRootFilesystem": true, + }, + ], + } count([d | decisions[d] not shisho.decision.is_allowed(d) @@ -75,25 +106,22 @@ test_task_with_privileged_container_denied if { {"services": []}, {"services": [{ "metadata": {"id": "aws-ecs-service|ap-northeast-1|example-ecs-cluster|example-ecs-service"}, - "taskDefinition": { - "arn": "arn:aws:ecs:ap-northeast-1:779392188153:task-definition/example-ecs-task-definition:1", - "containerDefinitions": [ - { - "__typename": "AWSECSLinuxContainerDefinition", - "name": "apache-helloworld", - "privileged": true, - "readonlyRootFilesystem": false, - }, - { - "__typename": "AWSECSLinuxContainerDefinition", - "name": "apache-helloworld", - "privileged": false, - "readonlyRootFilesystem": true, - }, - ], - }, + "taskDefinition": td, + }]}, + ]}}]}} + count([d | + decisions[d] + shisho.decision.is_allowed(d) + d.header.kind == shisho.decision.aws.ecs.container_fs_permission_kind + ]) == 1 with input as {"aws": {"accounts": [{"ecs": {"clusters": [ + {"services": []}, + {"services": [{ + "metadata": {"id": "aws-ecs-service|ap-northeast-1|example-ecs-cluster|example-ecs-service"}, + "taskDefinition": td, + "tags": [{"key": "foo", "value": "bar=piyo"}], }]}, ]}}]}} + with data.params as {"tag_exceptions": ["foo=bar=piyo"]} } test_task_with_privileged_container_allowed if {