diff --git a/sdk/rego b/sdk/rego index 87b64c9..9b57dbb 160000 --- a/sdk/rego +++ b/sdk/rego @@ -1 +1 @@ -Subproject commit 87b64c9464f4d200ee143d0dc84d86e4b263cc63 +Subproject commit 9b57dbb2606de5c289e80b80c32ef5385dca47e6 diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide.graphql new file mode 100644 index 0000000..b22d34f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide.graphql @@ -0,0 +1,25 @@ +{ + aws { + accounts { + cloudTrail { + trails { + metadata { + id + displayName + } + cloudWatchLogGroup { + arn + } + status { + latestCloudWatchLogsDeliveredAt + } + + tags { + key + value + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide.rego new file mode 100644 index 0000000..e7ec179 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide.rego @@ -0,0 +1,56 @@ +package policy.aws.cloudtrail.cloudwatch_logs_integration + +import data.shisho + +# this policy checks if the CloudWatch has not logged within the last 1 day +# please adjust the `must_alert_if_not_log_for` variable depending on your needs +must_alert_if_not_log_for := 1 + +decisions[d] { + account := input.aws.accounts[_] + trail := account.cloudTrail.trails[_] + + allowed := has_logs_within_recent_days(trail) + d := shisho.decision.aws.cloudtrail.cloudwatch_logs_integration({ + "allowed": allow_if_excluded(allowed, trail), + "subject": trail.metadata.id, + "payload": shisho.decision.aws.cloudtrail.cloudwatch_logs_integration_payload({"integrated": allowed}), + }) +} + +has_logs_within_recent_days(trail) { + trail.status.latestCloudWatchLogsDeliveredAt == null +} else { + # There is a log group associated with the trail + trail.cloudWatchLogGroup.arn != "" + + # The log delivery is still active within the specified days + lat := timestamp_ns(trail.status.latestCloudWatchLogsDeliveredAt) + logged_within_recent_days(lat, must_alert_if_not_log_for) +} else = false + +timestamp_ns(t) := 0 { + t == null +} else := time.parse_rfc3339_ns(t) + +logged_within_recent_days(ts, d) { + now := time.now_ns() + diff_ns := now - ts + + # confirm 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/cloudtrail/cloudwatch-logs-integration/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide_test.rego new file mode 100644 index 0000000..a0155c1 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cloudwatch-logs-integration/decide_test.rego @@ -0,0 +1,76 @@ +package policy.aws.cloudtrail.cloudwatch_logs_integration + +import data.shisho +import future.keywords + +now_ns := time.now_ns() + +today_string := date_string(now_ns) + +two_months_ago_string := date_string(time.add_date(now_ns, 0, -2, 0)) + +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])]) +} + +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) + +test_whether_cloudtrail_is_integrated_with_cloudwatch_logs if { + # check if the CloudTrail is integrated with CloudWatch logs + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "cloudWatchLogGroup": {"arn": "arn:aws:logs:ap-northeast-1:779392177777:log-group:test-trail-1/CloudTrailLogs:*"}, + "status": {"latestCloudWatchLogsDeliveredAt": null}, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "cloudWatchLogGroup": {"arn": "arn:aws:logs:ap-northeast-1:779392177777:log-group:test-trail-2/CloudTrailLogs:*"}, + "status": {"latestCloudWatchLogsDeliveredAt": today_string}, + }, + ]}}]}} + + # check if the CloudTrail is not integrated with CloudWatch logs + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "cloudWatchLogGroup": {"arn": "arn:aws:logs:ap-northeast-1:779392177777:log-group:test-trail-1/CloudTrailLogs:*"}, + "status": {"latestCloudWatchLogsDeliveredAt": ""}, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "cloudWatchLogGroup": {"arn": "arn:aws:logs:ap-northeast-1:779392177777:log-group:test-trail-2/CloudTrailLogs:*"}, + "status": {"latestCloudWatchLogsDeliveredAt": two_months_ago_string}, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "cloudWatchLogGroup": {"arn": ""}, + "status": {"latestCloudWatchLogsDeliveredAt": today_string}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide.graphql new file mode 100644 index 0000000..2270107 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide.graphql @@ -0,0 +1,20 @@ +{ + aws { + accounts { + cloudTrail { + trails { + metadata { + id + displayName + } + kmsKeyId + + tags { + key + value + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide.rego new file mode 100644 index 0000000..cc06b5f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide.rego @@ -0,0 +1,28 @@ +package policy.aws.cloudtrail.cmk_encryption + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + trail := account.cloudTrail.trails[_] + + d := shisho.decision.aws.cloudtrail.cmk_encryption({ + "allowed": allow_if_excluded(trail.kmsKeyId != "", trail), + "subject": trail.metadata.id, + "payload": shisho.decision.aws.cloudtrail.cmk_encryption_payload({"kms_key_id": trail.kmsKeyId}), + }) +} + +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/cloudtrail/cmk-encryption/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide_test.rego new file mode 100644 index 0000000..0bbd7e7 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/cmk-encryption/decide_test.rego @@ -0,0 +1,48 @@ +package policy.aws.cloudtrail.cmk_encryption + +import data.shisho +import future.keywords + +test_whether_cloudtrail_is_encrypted_by_kms_cmk if { + # check if the CloudTrail is encrypted by KMS CMK + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "kmsKeyId": "arn:aws:kms:ap-northeast-1:779392177777:key/6c7079dc-390c-4724-9e29-920317477777", + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "kmsKeyId": "arn:aws:kms:ap-northeast-1:779392177777:key/6c7079dc-390c-4724-9e29-920317488888", + }, + ]}}]}} + + # check if the CloudTrail is not encrypted by KMS CMK + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "kmsKeyId": "", + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "kmsKeyId": "", + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide.graphql new file mode 100644 index 0000000..0dcfde4 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide.graphql @@ -0,0 +1,32 @@ +{ + aws { + accounts { + cloudTrail { + trails { + metadata { + id + displayName + } + s3Bucket { + name + aclGrants { + grantee { + displayName + uri + } + permission + } + policy { + rawDocument + } + } + + tags { + key + value + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide.rego new file mode 100644 index 0000000..f1c3b88 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide.rego @@ -0,0 +1,67 @@ +package policy.aws.cloudtrail.log_bucket_accessibility + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + trail := account.cloudTrail.trails[_] + + accessible := is_publicly_accessible(trail.s3Bucket) + d := shisho.decision.aws.cloudtrail.log_bucket_accessibility({ + "allowed": allow_if_excluded(accessible == false, trail), + "subject": trail.metadata.id, + "payload": shisho.decision.aws.cloudtrail.log_bucket_accessibility_payload({ + "bucket_name": trail.s3Bucket.name, + "acl_rules": [{ + "grantee_url": grant.grantee.displayName, + "permission": grant.permission, + } | + grant := trail.s3Bucket.aclGrants[_] + ], + "bucket_policy_document": trail.s3Bucket.policy.rawDocument, + }), + }) +} + +is_publicly_accessible(s3Bucket) { + has_insecure_acl_grants(s3Bucket.aclGrants) +} else { + has_insecure_bucket_policy(s3Bucket.policy.rawDocument) +} else = false + +has_insecure_acl_grants(grants) { + grant := grants[_] + denied_uris := [ + "https://acs.amazonaws.com/groups/global/AllUsers", + "https://acs.amazonaws.com/groups/global/AuthenticatedUsers", + ] + denied_uris[_] == grant.grantee.uri +} else = false + +has_insecure_bucket_policy(raw_document) { + p := json.unmarshal(raw_document) + + statement := p.Statement[_] + statement.Effect == "Allow" + is_public_principal(statement.Principal) +} else = false + +is_public_principal(principal) { + principal == "*" +} else { + principal.AWS == "*" +} 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/cloudtrail/log-bucket-accessibility/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide_test.rego new file mode 100644 index 0000000..cd8db6e --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-bucket-accessibility/decide_test.rego @@ -0,0 +1,191 @@ +package policy.aws.cloudtrail.log_bucket_accessibility + +import data.shisho +import future.keywords + +test_whether_s3_bucket_for_cloudtrail_is_not_publicly_accessible if { + # check if the S3 bucket for CloudTrail is not publicly accessible + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 5 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6677777", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d01", + "uri": "", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-1\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-1\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6688888", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d02", + "uri": "", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6688888", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d03", + "uri": "", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Deny\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-4", + "displayName": "test-trail-4", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6688888", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d04", + "uri": "", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-5", + "displayName": "test-trail-5", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6688888", + "aclGrants": [], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-6", + "displayName": "test-trail-6", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6688888", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d04", + "uri": "", + }, + "permission": "FULL_CONTROL", + }], + "policy": "{}", + }, + }, + ]}}]}} + + # check if the S3 bucket for CloudTrail is publicly accessible by ACL grants + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6677777", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d01", + "uri": "https://acs.amazonaws.com/groups/global/AllUsers", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-1\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-1\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6688888", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d02", + "uri": "https://acs.amazonaws.com/groups/global/AuthenticatedUsers", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + ]}}]}} + + # check if the S3 bucket for CloudTrail is publicly accessible by a bucket policy + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6677777", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d01", + "uri": "", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-1\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-1\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "s3Bucket": { + "name": "aws-cloudtrail-logs-779392188153-b6688888", + "aclGrants": [{ + "grantee": { + "displayName": "test-grantee+d02", + "uri": "", + }, + "permission": "FULL_CONTROL", + }], + "policy": {"rawDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779392188153-b669bfe5\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\"}}},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-779397777777-b669bfe5/AWSLogs/779397777777/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudtrail:ap-northeast-1:779397777777:trail/test-trail-2\",\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"}, + }, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide.graphql new file mode 100644 index 0000000..6cab262 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide.graphql @@ -0,0 +1,20 @@ +{ + aws { + accounts { + cloudTrail { + trails { + metadata { + id + displayName + } + logFileValidationEnabled + + tags { + key + value + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide.rego new file mode 100644 index 0000000..b9e56a3 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide.rego @@ -0,0 +1,30 @@ +package policy.aws.cloudtrail.log_file_validation + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + + trail := account.cloudTrail.trails[_] + allowed := trail.logFileValidationEnabled + + d := shisho.decision.aws.cloudtrail.log_file_validation({ + "allowed": allow_if_excluded(allowed, trail), + "subject": trail.metadata.id, + "payload": shisho.decision.aws.cloudtrail.log_file_validation_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/cloudtrail/log-file-validation/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide_test.rego new file mode 100644 index 0000000..f717aa3 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/log-file-validation/decide_test.rego @@ -0,0 +1,48 @@ +package policy.aws.cloudtrail.log_file_validation + +import data.shisho +import future.keywords + +test_whether_log_validation_for_cloudtrail_is_enabled if { + # check if the log validation for CloudTrail is enabled + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "logFileValidationEnabled": true, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "logFileValidationEnabled": true, + }, + ]}}]}} + + # check if the log validation for CloudTrail is not enabled + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "logFileValidationEnabled": false, + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "logFileValidationEnabled": false, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/manifest.yaml new file mode 100644 index 0000000..db39c3a --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/manifest.yaml @@ -0,0 +1,95 @@ +version: 0.1.0 + +id: "prebundle-aws-cloudtrail" +name: "Prebundle: Review AWS CloudTrail posture" + +triggers: + schedule: + - cron: "*/10 * * * *" + +jobs: + - id: usage + name: Review CloudTrail is enabled in all regions + decide: + rego: !include usage/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 usage/decide.graphql + - id: log-bucket-accessibility + name: Review the S3 bucket for CloudTrail logs is not publicly accessible + decide: + rego: !include log-bucket-accessibility/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 log-bucket-accessibility/decide.graphql + - id: log-file-validation + name: Review CloudTrail log file validation is enabled + decide: + rego: !include log-file-validation/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 log-file-validation/decide.graphql + - id: cmk-encryption + name: Review CloudTrail logs are encrypted at rest using KMS CMKs + decide: + rego: !include cmk-encryption/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 cmk-encryption/decide.graphql + - id: cloudwatch-logs-integration + name: Review CloudTrail trails are integrated with CloudWatch Logs + decide: + rego: !include cloudwatch-logs-integration/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 cloudwatch-logs-integration/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide.graphql new file mode 100644 index 0000000..6fbb676 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide.graphql @@ -0,0 +1,26 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + arn + + isMultiRegionTrail + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + includeManagementEvents + readWriteType + } + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide.rego new file mode 100644 index 0000000..4bb6b44 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide.rego @@ -0,0 +1,29 @@ +package policy.aws.cloudtrail.usage + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + + d := shisho.decision.aws.cloudtrail.usage({ + "allowed": has_compliant_trail(account.cloudTrail.trails), + "subject": account.metadata.id, + "payload": shisho.decision.aws.cloudtrail.usage_payload({"trail_arns": [t.arn | t := account.cloudTrail.trails[_]]}), + }) +} + +has_compliant_trail(trails) { + trail := trails[_] + + # The trail is enabled in all regions ... + trail.isMultiRegionTrail == true + + # ... and actively logging ... + trail.status.isLogging == true + + # ... and is configured to log all events ... + count(trail.eventSelectors) > 0 + eventSelector := trail.eventSelectors[_] + eventSelector.includeManagementEvents == true + eventSelector.readWriteType == "ALL" +} else = false diff --git a/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide_test.rego new file mode 100644 index 0000000..236738b --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/cloudtrail/usage/decide_test.rego @@ -0,0 +1,132 @@ +package policy.aws.cloudtrail.usage + +import data.shisho +import future.keywords + +test_whether_cloudtrail_is_ready_to_track_logs_in_all_regions if { + # check if the CloudTrail is ready to track logs in all regions + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "cloudTrail": {"trails": [ + { + "arn": "tbd", + "isMultiRegionTrail": false, + "status": {"isLogging": true}, + "eventSelectors": [{ + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "arn": "tbd", + "isMultiRegionTrail": true, + "status": {"isLogging": true}, + "eventSelectors": [{ + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + ]}, + }, + { + "metadata": { + "id": "aws-account|779392188888", + "displayName": "779392188888", + }, + "cloudTrail": {"trails": [{ + "arn": "tbd", + "isMultiRegionTrail": true, + "status": {"isLogging": true}, + "eventSelectors": [{ + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + }, + ]}} + + # check if the CloudTrail is not ready to track logs in all regions + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 4 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "cloudTrail": {"trails": [ + { + "arn": "tbd", + "isMultiRegionTrail": false, + "status": {"isLogging": false}, + "eventSelectors": [{ + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }, + { + "arn": "tbd", + "isMultiRegionTrail": false, + "status": {"isLogging": true}, + "eventSelectors": [{ + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + ]}, + }, + { + "metadata": { + "id": "aws-account|779392188888", + "displayName": "779392188888", + }, + "cloudTrail": {"trails": [{ + "arn": "tbd", + "isMultiRegionTrail": true, + "status": {"isLogging": false}, + "eventSelectors": [{ + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392199999", + "displayName": "779392199999", + }, + "cloudTrail": {"trails": [{ + "arn": "tbd", + "isMultiRegionTrail": true, + "status": {"isLogging": true}, + "eventSelectors": [{ + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392100000", + "displayName": "779392100000", + }, + "cloudTrail": {"trails": [{ + "arn": "tbd", + "isMultiRegionTrail": true, + "status": {"isLogging": true}, + "eventSelectors": [{ + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/config/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/config/manifest.yaml new file mode 100644 index 0000000..c9d6023 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/config/manifest.yaml @@ -0,0 +1,27 @@ +version: 0.1.0 + +id: "prebundle-aws-config" +name: "Prebundle: Review AWS Config posture" + +triggers: + schedule: + - cron: "*/10 * * * *" + +jobs: + - id: recorder-status + name: Review AWS Config is enabled in all regions + decide: + rego: !include recorder-status/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 recorder-status/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide.graphql new file mode 100644 index 0000000..7296681 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide.graphql @@ -0,0 +1,29 @@ +{ + aws { + accounts { + metadata { + id + } + + config { + recorders { + name + region + metadata { + id + displayName + } + recordingGroup { + allSupported + includeGlobalResourceTypes + resourceTypes + } + status { + lastStatus + recording + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide.rego new file mode 100644 index 0000000..5eaa544 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide.rego @@ -0,0 +1,60 @@ +package policy.aws.config.recorder_status + +import data.shisho +import future.keywords.every + +# Add new regions to the list below if you want to ignore some regions in this policies +region_exceptions := (shisho.thirdparty.aws.opt_in_regions | shisho.thirdparty.aws.china_regions) | shisho.thirdparty.aws.gov_regions + +decisions[d] { + account := input.aws.accounts[_] + d := shisho.decision.aws.config.recorder_status({ + "allowed": all_regions_covered(account.config.recorders), + "subject": account.metadata.id, + "payload": shisho.decision.aws.config.recorder_status_payload({ + "missing_regions": [r | + r := shisho.thirdparty.aws.regions[_] + not is_covered(r, [r.region | + r := account.config.recorders[_] + is_valid_recorder(r) + ]) + ], + "recorders": [{ + "name": recorder.name, + "region": recorder.region, + "recording_group": { + "all_supported": recorder.recordingGroup.allSupported, + "include_global_resource_types": recorder.recordingGroup.includeGlobalResourceTypes, + "resource_types": recorder.recordingGroup.resourceTypes, + }, + } | + recorder := account.config.recorders[_] + ], + }), + }) +} + +all_regions_covered(recorders) { + covered_regions := {r.region | + r := recorders[_] + is_valid_recorder(r) + } + + every r in shisho.thirdparty.aws.regions { + is_covered(r, covered_regions) + } +} else := false + +is_valid_recorder(r) { + r.recordingGroup.allSupported == true + r.recordingGroup.includeGlobalResourceTypes == true + + r.status.lastStatus == "SUCCESS" + r.status.recording == true +} else := false + +is_covered(r, covered_regions) { + r in covered_regions +} else { + r in region_exceptions +} else := false diff --git a/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide_test.rego new file mode 100644 index 0000000..2bcaabe --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/config/recorder-status/decide_test.rego @@ -0,0 +1,99 @@ +package policy.aws.config.recorder_status + +import data.shisho +import future.keywords + +test_policy_config_is_enabled_and_recording if { + # check if Config is enabled and recording + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": {"id": "aws-account|779392187777"}, + "config": {"recorders": [ + { + "name": "aa", + "metadata": { + "id": "aws-config-recorder|ap-northeast-1|test-recorder-1", + "displayName": "test-recorder-1", + }, + "region": "ap-northeast-1", + "recordingGroup": { + "allSupported": true, + "includeGlobalResourceTypes": true, + "resourceTypes": [], + }, + "status": { + "lastStatus": "SUCCESS", + "recording": true, + }, + }, + { + "name": "aa", + "metadata": { + "id": "aws-config-recorder|ap-northeast-1|test-recorder-2", + "displayName": "test-recorder-2", + }, + "region": "ap-northeast-2", + "recordingGroup": { + "allSupported": true, + "includeGlobalResourceTypes": true, + "resourceTypes": [], + }, + "status": { + "lastStatus": "SUCCESS", + "recording": true, + }, + }, + ]}, + }]}} + with data.shisho.thirdparty.aws.regions as {"ap-northeast-1", "ap-northeast-2"} + + # check if Config is enabled and recording + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": {"id": "aws-account|779392187777"}, + "config": {"recorders": [{ + "name": "aa", + "metadata": { + "id": "aws-config-recorder|ap-northeast-1|test-recorder-1", + "displayName": "test-recorder-1", + }, + "region": "ap-northeast-1", + "recordingGroup": { + "allSupported": true, + "includeGlobalResourceTypes": true, + "resourceTypes": [], + }, + "status": { + "lastStatus": "SUCCESS", + "recording": true, + }, + }]}, + }, + { + "metadata": {"id": "aws-account|779392187888"}, + "config": {"recorders": [{ + "name": "aa", + "metadata": { + "id": "aws-config-recorder|ap-northeast-1|test-recorder-1", + "displayName": "test-recorder-1", + }, + "region": "ap-northeast-2", + "recordingGroup": { + "allSupported": true, + "includeGlobalResourceTypes": true, + "resourceTypes": [], + }, + "status": { + "lastStatus": "SUCCESS", + "recording": true, + }, + }]}, + }, + ]}} + with data.shisho.thirdparty.aws.regions as {"ap-northeast-1", "ap-northeast-2"} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide.graphql new file mode 100644 index 0000000..eb9dd19 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide.graphql @@ -0,0 +1,16 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + iam { + accessAnalyzers { + region + name + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide.rego new file mode 100644 index 0000000..83ef686 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide.rego @@ -0,0 +1,46 @@ +package policy.aws.iam.access_analyzers + +import data.shisho +import future.keywords.every + +# Add new regions to the list below if you want to ignore some regions in this policies +region_exceptions := (shisho.thirdparty.aws.opt_in_regions | shisho.thirdparty.aws.china_regions) | shisho.thirdparty.aws.gov_regions + +decisions[d] { + account := input.aws.accounts[_] + analyzers := analyzer_names(account.iam.accessAnalyzers) + + d := shisho.decision.aws.iam.access_analyzers({ + "allowed": all_regions_covered(analyzers), + "subject": account.metadata.id, + "payload": shisho.decision.aws.iam.access_analyzers_payload({ + "missing_regions": [r | + r := shisho.thirdparty.aws.regions[_] + not is_covered(r, {a.region | a := analyzers[_]}) + ], + "analyzers": analyzers, + }), + }) +} + +all_regions_covered(analyzers) { + covered_regions := {a.region | a := analyzers[_]} + every r in shisho.thirdparty.aws.regions { + is_covered(r, covered_regions) + } +} else := false + +is_covered(r, covered_regions) { + r in covered_regions +} else { + r in region_exceptions +} else := false + +analyzer_names(analyzers) := x { + x := [{ + "analyzer_name": analyzer.name, + "region": analyzer.region, + } | + analyzer := analyzers[_] + ] +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide_test.rego new file mode 100644 index 0000000..c91f841 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/access-analyzers/decide_test.rego @@ -0,0 +1,53 @@ +package policy.aws.iam.access_analyzers + +import data.shisho +import future.keywords + +test_whether_the_access_analyzers_is_enabled_for_each_region if { + # check if the Access Analyzer is enabled for each region + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": { + "id": "aws-account|779392188888", + "displayName": "779392188888", + }, + "iam": {"accessAnalyzers": [ + { + "region": "ap-northeast-1", + "name": "ConsoleAnalyzer-181dba79-617f-4755-a816-b9055b188888", + }, + { + "region": "ap-northeast-2", + "name": "ConsoleAnalyzer-181dba79-617f-4755-a816-b9055b188888", + }, + ]}, + }]}} + with data.shisho.thirdparty.aws.regions as {"ap-northeast-1", "ap-northeast-2"} + + # check if the Access Analyzer is enabled for each region + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "iam": {"accessAnalyzers": []}, + }, + { + "metadata": { + "id": "aws-account|779392188888", + "displayName": "779392188888", + }, + "iam": {"accessAnalyzers": [{ + "region": "ap-northeast-1", + "name": "ConsoleAnalyzer-181dba79-617f-4755-a816-b9055b188888", + }]}, + }, + ]}} + with data.shisho.thirdparty.aws.regions as {"ap-northeast-1", "ap-northeast-2"} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide.graphql new file mode 100644 index 0000000..54e81f3 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide.graphql @@ -0,0 +1,15 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + alternateContactState { + billingContactRegistered + securityContactRegistered + operationsContactRegistered + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide.rego new file mode 100644 index 0000000..2fdc5ff --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide.rego @@ -0,0 +1,19 @@ +package policy.aws.iam.account_alternate_contact + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + + contact_state := account.alternateContactState + + d := shisho.decision.aws.iam.account_alternate_contact({ + "allowed": contact_state.securityContactRegistered, + "subject": account.metadata.id, + "payload": shisho.decision.aws.iam.account_alternate_contact_payload({ + "contact_for_security_registered": contact_state.securityContactRegistered, + "contact_for_billing_registered": contact_state.billingContactRegistered, + "contact_for_operations_registered": contact_state.operationsContactRegistered, + }), + }) +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide_test.rego new file mode 100644 index 0000000..e8d5bdd --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/account-alternate-contact/decide_test.rego @@ -0,0 +1,64 @@ +package policy.aws.iam.account_alternate_contact + +import data.shisho +import future.keywords + +test_whether_the_alternate_contact_of_accounts_is_allowed if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "alternateContactState": { + "billingContactRegistered": true, + "securityContactRegistered": true, + "operationsContactRegistered": false, + }, + }, + { + "metadata": { + "id": "aws-account|779397777778", + "displayName": "779397777778", + }, + "alternateContactState": { + "billingContactRegistered": false, + "securityContactRegistered": true, + "operationsContactRegistered": false, + }, + }, + ]}} +} + +test_whether_the_alternate_contact_of_accounts_is_denied if { + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "alternateContactState": { + "billingContactRegistered": true, + "securityContactRegistered": false, + "operationsContactRegistered": false, + }, + }, + { + "metadata": { + "id": "aws-account|779397777778", + "displayName": "779397777778", + }, + "alternateContactState": { + "billingContactRegistered": false, + "securityContactRegistered": false, + "operationsContactRegistered": false, + }, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide.graphql new file mode 100644 index 0000000..8e7e1ee --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide.graphql @@ -0,0 +1,32 @@ +{ + aws { + accounts { + iam { + users { + metadata { + id + } + name + } + + credentialReport { + metadata { + id + displayName + } + contents { + user + + passwordEnabled + passwordLastUsedAt + + accessKey1Active + accessKey1LastUsedAt + accessKey2Active + accessKey2LastUsedAt + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide.rego new file mode 100644 index 0000000..3cfb31f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide.rego @@ -0,0 +1,50 @@ +package policy.aws.iam.console_user_keys + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + + # Prepare a map from a user name to a user resource ID in Shisho Cloud + user_map := {user.name: user.metadata.id | + user := account.iam.users[_] + } + + content := account.iam.credentialReport.contents[_] + subject := user_map[content.user] + + d := shisho.decision.aws.iam.console_user_keys({ + "allowed": has_console_password_and_unused_access_keys(content) == false, + "subject": subject, + "payload": shisho.decision.aws.iam.console_user_keys_payload({ + "has_console_password": content.passwordEnabled, + "has_unused_console_password": has_unused_console_password(content), + "has_access_key": has_access_keys(content), + "has_unused_access_key": has_unused_access_keys(content), + }), + }) +} + +has_console_password_and_unused_access_keys(content) { + content.passwordEnabled + has_unused_access_keys(content) +} else = false + +has_unused_console_password(content) { + content.passwordEnabled + content.passwordLastUsedAt == null +} else = false + +has_access_keys(content) { + content.accessKey1Active +} else { + content.accessKey2Active +} else = false + +has_unused_access_keys(content) { + content.accessKey1Active + content.accessKey1LastUsedAt == null +} else { + content.accessKey2Active + content.accessKey2LastUsedAt == null +} else = false diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide_test.rego new file mode 100644 index 0000000..8b53ebd --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/console-user-keys/decide_test.rego @@ -0,0 +1,132 @@ +package policy.aws.iam.console_user_keys + +import data.shisho +import future.keywords + +test_whether_the_access_keys_are_not_enabled_for_user_who_has_password if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 4 with input as {"aws": {"accounts": [{"iam": { + "users": [ + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBB"}, + "name": "test-user", + }, + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBC"}, + "name": "test-user-2", + }, + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBD"}, + "name": "test-user-3", + }, + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBE"}, + "name": "test-user-4", + }, + ], + "credentialReport": { + "metadata": { + "id": "aws-iam-credential-report|77939277777", + "displayName": "Report for 779392177777", + }, + "contents": [ + { + "user": "", + "passwordEnabled": false, + "accessKey1Active": false, + "accessKey1LastUsedAt": null, + "accessKey2Active": false, + "accessKey2LastUsedAt": null, + }, + # console user + used access key + { + "user": "test-user", + "passwordEnabled": true, + "accessKey1Active": false, + "accessKey1LastUsedAt": null, + "accessKey2Active": true, + "accessKey2LastUsedAt": "2023-01-01T00:00:00Z", + }, + # console user + used access key + { + "user": "test-user-2", + "passwordEnabled": true, + "accessKey1Active": true, + "accessKey1LastUsedAt": "2023-01-01T00:00:00Z", + "accessKey2Active": false, + "accessKey2LastUsedAt": null, + }, + # key-only user + { + "user": "test-user-3", + "passwordEnabled": false, + "accessKey1Active": true, + "accessKey1LastUsedAt": null, + "accessKey2Active": false, + "accessKey2LastUsedAt": null, + }, + # key-only user + { + "user": "test-user-4", + "passwordEnabled": false, + "accessKey1Active": false, + "accessKey1LastUsedAt": null, + "accessKey2Active": true, + "accessKey2LastUsedAt": null, + }, + ], + }, + }}]}} + + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"iam": { + "users": [ + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBB"}, + "name": "test-user", + }, + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBC"}, + "name": "test-user-2", + }, + ], + "credentialReport": { + "metadata": { + "id": "aws-iam-credential-report|77939277777", + "displayName": "Report for 779392177777", + }, + "contents": [ + { + "user": "", + "passwordEnabled": false, + "accessKey1Active": false, + "accessKey1LastUsedAt": null, + "accessKey2Active": false, + "accessKey2LastUsedAt": null, + }, + # console user + unused access key + { + "user": "test-user", + "passwordEnabled": true, + "accessKey1Active": false, + "accessKey1LastUsedAt": null, + "accessKey2Active": true, + "accessKey2LastUsedAt": null, + }, + # console user + unused access key + { + "user": "test-user-2", + "passwordEnabled": true, + "accessKey1Active": true, + "accessKey1LastUsedAt": null, + "accessKey2Active": false, + "accessKey2LastUsedAt": null, + }, + ], + }, + }}]}} +} 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 51fb2a4..d6ccaeb 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 @@ -7,10 +7,8 @@ id } accessKeys { + id createdAt - lastUsed { - lastUsedAt - } } tags { 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 ce20c24..cdd9de6 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 @@ -11,7 +11,6 @@ decisions[d] { user := account.iam.users[_] keys := keys_requiring_rotation(user.accessKeys) - trace(sprintf("name=%v", [keys])) allowed := count(keys) == 0 d := shisho.decision.aws.iam.key_rotation({ @@ -38,20 +37,11 @@ keys_requiring_rotation(keys) := x { # the key needs rotation... needs_rotation(key) { - # (1) if the key has never been used, and `days_of_accepted_age` days have passed since the key was created - key.lastUsed == null now := time.now_ns() t := time.parse_rfc3339_ns(key.createdAt) now - t > (((1000000000 * 60) * 60) * 24) * days_of_accepted_age -} else { - # (2) if the key has been used so far, and `days_of_accepted_age` days have passed since the key was used last - key.lastUsed != null - now := time.now_ns() - - t := time.parse_rfc3339_ns(key.lastUsed.lastUsedAt) - now - t > (((1000000000 * 60) * 60) * 24) * days_of_accepted_age -} else = false +} else := false allow_if_excluded(allowed, r) { data.params != null 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 b9cafcf..6ed7535 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 @@ -24,36 +24,13 @@ test_whether_all_access_keys_are_rotated_within_90_days if { count([d | decisions[d] shisho.decision.is_allowed(d) - ]) == 3 with input as {"aws": {"accounts": [{"iam": {"users": [ + ]) == 2 with input as {"aws": {"accounts": [{"iam": {"users": [ { "metadata": {"id": "aws-iam-user|AIDA3K53E73AAAAAAAAAA"}, - "accessKeys": [ - { - "id": "1", - "createdAt": today_string, - "lastUsed": {"lastUsedAt": today_string}, - }, - { - "id": "2", - "createdAt": "2021-03-17T11:49:31Z", - "lastUsed": {"lastUsedAt": today_string}, - }, - ], - }, - { - "metadata": {"id": "aws-iam-user|AIDA3K53E73BBBBBBBBBB"}, - "accessKeys": [ - { - "id": "1", - "createdAt": today_string, - "lastUsed": null, - }, - { - "id": "2", - "createdAt": "2022-03-20T11:49:31Z", - "lastUsed": {"lastUsedAt": today_string}, - }, - ], + "accessKeys": [{ + "id": "1", + "createdAt": today_string, + }], }, { "metadata": {"id": "aws-iam-user|AIDA3K53E73CCCCCCCCCC"}, @@ -65,51 +42,26 @@ test_whether_all_access_keys_are_rotated_within_90_days if { count([d | decisions[d] not shisho.decision.is_allowed(d) - ]) == 3 with input as {"aws": {"accounts": [{"iam": {"users": [ + ]) == 2 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}, - }, - ], - }, - { - "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}, + "createdAt": today_string, }, ], }, { - "metadata": {"id": "aws-iam-user|AIDA3K53E73CCCCCCCCCC"}, - "accessKeys": [ - { - "id": "1", - "createdAt": four_months_ago_string, - "lastUsed": {"lastUsedAt": four_months_ago_string}, - }, - { - "id": "2", - "createdAt": today_string, - "lastUsed": {"lastUsedAt": today_string}, - }, - ], + "metadata": {"id": "aws-iam-user|AIDA3K53E73BBBBBBBBBB"}, + "accessKeys": [{ + "id": "1", + "createdAt": four_months_ago_string, + }], }, ]}}]}} @@ -120,34 +72,18 @@ test_whether_all_access_keys_are_rotated_within_90_days if { ]) == 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}, - }, - ], + "accessKeys": [{ + "id": "1", + "createdAt": four_months_ago_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}, - }, - ], + "accessKeys": [{ + "id": "1", + "createdAt": four_months_ago_string, + }], "tags": [{"key": "foo", "value": "unrelated"}], }, ]}}]}} 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 9e8abe4..61a3b94 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/iam/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/manifest.yaml @@ -9,7 +9,7 @@ triggers: jobs: - id: credentials-inventory - name: Review the credentials + name: Review the credential usage to find unused ones decide: rego: !include credentials-inventory/decide.rego with: @@ -67,7 +67,7 @@ jobs: input: schema: !include password-reuse/decide.graphql - id: permissive-policy-limitation - name: Review the policy configuration + name: Find permissive IAM policies decide: rego: !include permissive-policy-limitation/decide.rego with: @@ -131,3 +131,139 @@ jobs: values: [] input: schema: !include root-user-usage/decide.graphql + - id: account-alternate-contact + name: Review that security contact information is registered + decide: + rego: !include account-alternate-contact/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 account-alternate-contact/decide.graphql + - id: user-group-permission-assignment + name: Review IAM users receive permissions only through groups + decide: + rego: !include user-group-permission-assignment/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 user-group-permission-assignment/decide.graphql + - id: console-user-keys + name: Review access keys during initial user setup for all IAM users with a console password + decide: + rego: !include console-user-keys/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 console-user-keys/decide.graphql + - id: user-mfa + name: Review multi-factor authentication (MFA) is enabled for all IAM users that have a console password + decide: + rego: !include user-mfa/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 user-mfa/decide.graphql + - id: server-certificates + name: Review that all the expired SSL/TLS certificates stored in AWS IAM are removed + decide: + rego: !include server-certificates/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 server-certificates/decide.graphql + - id: access-analyzers + name: Review that IAM Access analyzer is enabled for all regions + decide: + rego: !include access-analyzers/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 access-analyzers/decide.graphql + - id: role-for-support + name: Review that a support role has been created to manage incidents with AWS Support + decide: + rego: !include role-for-support/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 role-for-support/decide.graphql + - id: user-available-access-keys + name: Review that there is only one active access key available for any single IAM user + decide: + rego: !include user-available-access-keys/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 user-available-access-keys/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide.graphql new file mode 100644 index 0000000..41a6cab --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide.graphql @@ -0,0 +1,26 @@ +{ + aws { + accounts { + metadata { + displayName + id + } + iam { + policies { + name + entities { + roles { + name + } + users { + name + } + groups { + name + } + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide.rego new file mode 100644 index 0000000..4ffb588 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide.rego @@ -0,0 +1,48 @@ +package policy.aws.iam.role_for_support + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + + roles := roles_for_support(account.iam.policies) + users := users_for_support(account.iam.policies) + groups := groups_for_support(account.iam.policies) + + d := shisho.decision.aws.iam.role_for_support({ + "allowed": count(array.concat(array.concat(roles, users), groups)) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.iam.role_for_support_payload({ + "attached_roles": roles, + "attached_groups": groups, + "attached_users": users, + }), + }) +} + +roles_for_support(policies) := x { + x := [r.name | + policy := policies[_] + policy.name == "AWSSupportAccess" + count(policy.entities.roles) > 0 + r := policy.entities.roles[_] + ] +} + +users_for_support(policies) := x { + x := [u.name | + policy := policies[_] + policy.name == "AWSSupportAccess" + count(policy.entities.users) > 0 + u := policy.entities.users[_] + ] +} + +groups_for_support(policies) := x { + x := [g.name | + policy := policies[_] + policy.name == "AWSSupportAccess" + count(policy.entities.groups) > 0 + g := policy.entities.groups[_] + ] +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide_test.rego new file mode 100644 index 0000000..b46f4e4 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/role-for-support/decide_test.rego @@ -0,0 +1,72 @@ +package policy.aws.iam.role_for_support + +import data.shisho +import future.keywords + +test_whether_the_roles_is_created_for_aws_support if { + # check if the role is created for AWS support + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "iam": {"policies": [ + { + "name": "AWSSupportAccess", + "entities": {"roles": [{"name": "test-role-1"}]}, + }, + { + "name": "AWSLambdaBasicExecutionRole-test-policy-name-2", + "entities": {"roles": [{"name": "test-role-2"}]}, + }, + ]}, + }, + { + "metadata": { + "id": "aws-account|779392188888", + "displayName": "779392188888", + }, + "iam": {"policies": [ + { + "name": "AWSSupportAccess", + "entities": {"roles": [{"name": "test-role-1"}]}, + }, + { + "name": "AWSLambdaBasicExecutionRole-test-policy-name-2", + "entities": {"roles": [{"name": "test-role-2"}]}, + }, + ]}, + }, + ]}} + + # check if the role is not created for AWS support + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "iam": {"policies": [{ + "name": "AWSLambdaBasicExecutionRole-test-policy-name-2", + "entities": {"roles": [{"name": "test-role-2"}]}, + }]}, + }, + { + "metadata": { + "id": "aws-account|779392188888", + "displayName": "779392188888", + }, + "iam": {"policies": [{ + "name": "AWSLambdaBasicExecutionRole-test-policy-name-2", + "entities": {"roles": [{"name": "test-role-2"}]}, + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide.graphql new file mode 100644 index 0000000..e54e192 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide.graphql @@ -0,0 +1,16 @@ +{ + aws { + accounts { + iam { + serverCertificates { + metadata { + id + displayName + } + name + expiredAt + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide.rego new file mode 100644 index 0000000..185a91d --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide.rego @@ -0,0 +1,25 @@ +package policy.aws.iam.server_certificates + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + certificate := account.iam.serverCertificates[_] + + d := shisho.decision.aws.iam.server_certificates({ + "allowed": expired_certificate(certificate) == false, + "subject": certificate.metadata.id, + "payload": shisho.decision.aws.iam.server_certificates_payload({ + "name": certificate.name, + "expired_at": certificate.expiredAt, + }), + }) +} + +expired_certificate(certificate) { + timestamp_ns(certificate.expiredAt) < time.now_ns() +} else = false + +timestamp_ns(t) := 0 { + t == null +} else := time.parse_rfc3339_ns(t) diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide_test.rego new file mode 100644 index 0000000..3e4053f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/server-certificates/decide_test.rego @@ -0,0 +1,104 @@ +package policy.aws.iam.server_certificates + +import data.shisho +import future.keywords + +in_one_year_string := date_string(time.add_date(time.now_ns(), 1, 0, 0)) + +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])]) +} + +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) + +test_whether_the_server_certificates_are_not_expired if { + # check if the server certificates are not expired + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 4 with input as {"aws": {"accounts": [ + {"iam": {"serverCertificates": [ + { + "metadata": { + "id": "aws-iam-server-certificate|7e81eedf-6bfa-4e28-b1be-f527615fab8b", + "displayName": "test-certificate-1", + }, + "name": "test-certificate-1", + "expiredAt": in_one_year_string, + }, + { + "metadata": { + "id": "aws-iam-server-certificate|f85496c1-970a-4ab1-8204-63d04f4cec01", + "displayName": "test-certificate-2", + }, + "name": "test-certificate-2", + "expiredAt": in_one_year_string, + }, + ]}}, + {"iam": {"serverCertificates": [ + { + "metadata": { + "id": "aws-iam-server-certificate|74430d6f-8630-4b8b-99f9-29e031136836", + "displayName": "test-certificate-3", + }, + "name": "test-certificate-3", + "expiredAt": in_one_year_string, + }, + { + "metadata": { + "id": "aws-iam-server-certificate|92424574-3932-4963-a20b-9fb8c608513e", + "displayName": "test-certificate-4", + }, + "name": "test-certificate-4", + "expiredAt": in_one_year_string, + }, + ]}}, + ]}} + + # check if the server certificates are expired + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 4 with input as {"aws": {"accounts": [ + {"iam": {"serverCertificates": [ + { + "metadata": { + "id": "aws-iam-server-certificate|7e81eedf-6bfa-4e28-b1be-f527615fab8b", + "displayName": "test-certificate-1", + }, + "name": "test-certificate-1", + "expiredAt": "2020-05-01T10:00:00Z", + }, + { + "metadata": { + "id": "aws-iam-server-certificate|f85496c1-970a-4ab1-8204-63d04f4cec01", + "displayName": "test-certificate-2", + }, + "name": "test-certificate-2", + "expiredAt": "2021-05-01T10:00:00Z", + }, + ]}}, + {"iam": {"serverCertificates": [ + { + "metadata": { + "id": "aws-iam-server-certificate|74430d6f-8630-4b8b-99f9-29e031136836", + "displayName": "test-certificate-3", + }, + "name": "test-certificate-3", + "expiredAt": "2022-05-01T10:00:00Z", + }, + { + "metadata": { + "id": "aws-iam-server-certificate|92424574-3932-4963-a20b-9fb8c608513e", + "displayName": "test-certificate-4", + }, + "name": "test-certificate-4", + "expiredAt": "2023-05-01T10:00:00Z", + }, + ]}}, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide.graphql new file mode 100644 index 0000000..9db4818 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide.graphql @@ -0,0 +1,18 @@ +{ + aws { + accounts { + iam { + users { + metadata { + id + displayName + } + accessKeys { + id + status + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide.rego new file mode 100644 index 0000000..877b9da --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide.rego @@ -0,0 +1,24 @@ +package policy.aws.iam.user_available_access_keys + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + user = account.iam.users[_] + + keys := owned_access_keys(user.accessKeys) + allowed := count(keys) <= 1 + + d := shisho.decision.aws.iam.user_available_access_keys({ + "allowed": allowed, + "subject": user.metadata.id, + "payload": shisho.decision.aws.iam.user_available_access_keys_payload({"access_key_ids": keys}), + }) +} + +owned_access_keys(access_keys) := x { + x := [key.id | + key := access_keys[_] + key.status == "ACTIVE" + ] +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide_test.rego new file mode 100644 index 0000000..3c9798c --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-available-access-keys/decide_test.rego @@ -0,0 +1,98 @@ +package policy.aws.iam.user_available_access_keys + +import data.shisho +import future.keywords + +test_whether_the_user_owns_multipl_available_access_keys if { + # check if the user does not own multiple available access keys + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"aws": {"accounts": [{"iam": {"users": [ + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZAAAAA", + "displayName": "test-user-1", + }, + "accessKeys": [{ + "id": "AKIA3K53E7345EVAAAAA", + "status": "ACTIVE", + }], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZBBBB", + "displayName": "test-user-2", + }, + "accessKeys": [{ + "id": "AKIA3K53E7345EVBBBBB", + "status": "ACTIVE", + }], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZCCCCC", + "displayName": "test-user-3", + }, + "accessKeys": [{ + "id": "AKIA3K53E7345EVCCCCC", + "status": "ACTIVE", + }], + }, + ]}}]}} + + # check if the user owns multiple available access keys + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"aws": {"accounts": [{"iam": {"users": [ + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZAAAAA", + "displayName": "test-user-1", + }, + "accessKeys": [ + { + "id": "AKIA3K53E7345EVAAAAA", + "status": "ACTIVE", + }, + { + "id": "AKIA3K53E7345EVAAAA2", + "status": "ACTIVE", + }, + ], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZBBBB", + "displayName": "test-user-2", + }, + "accessKeys": [ + { + "id": "AKIA3K53E7345EVBBBBB", + "status": "ACTIVE", + }, + { + "id": "AKIA3K53E7345EVBBBB2", + "status": "ACTIVE", + }, + ], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZCCCCC", + "displayName": "test-user-3", + }, + "accessKeys": [ + { + "id": "AKIA3K53E7345EVCCCCC", + "status": "ACTIVE", + }, + { + "id": "AKIA3K53E7345EVCCCC2", + "status": "ACTIVE", + }, + ], + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide.graphql new file mode 100644 index 0000000..828532d --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide.graphql @@ -0,0 +1,22 @@ +{ + aws { + accounts { + iam { + users { + metadata { + id + displayName + } + policies { + ... on AWSIAMUserInlinePolicy { + name + } + ... on AWSIAMUserManagedPolicy { + name + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide.rego new file mode 100644 index 0000000..b64f082 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide.rego @@ -0,0 +1,22 @@ +package policy.aws.iam.user_group_permission_assignment + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + user = account.iam.users[_] + + policies := attached_policy_names(user.policies) + + d := shisho.decision.aws.iam.user_group_permission_assignment({ + "allowed": count(policies) == 0, + "subject": user.metadata.id, + "payload": shisho.decision.aws.iam.user_group_permission_assignment_payload({"attached_policy_names": policies}), + }) +} + +attached_policy_names(policies) := x { + x := [policy.name | + policy := policies[_] + ] +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide_test.rego new file mode 100644 index 0000000..2061b6e --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-group-permission-assignment/decide_test.rego @@ -0,0 +1,62 @@ +package policy.aws.iam.user_group_permission_assignment + +import data.shisho +import future.keywords + +test_whether_the_user_has_policies_directly if { + # check if the user does not have any policies directly + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"aws": {"accounts": [{"iam": {"users": [ + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZAAAAA", + "displayName": "test-user-1", + }, + "policies": [], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZBBBB", + "displayName": "test-user-2", + }, + "policies": [], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZCCCCC", + "displayName": "test-user-3", + }, + "policies": [], + }, + ]}}]}} + + # check if the user has some policies directly + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"aws": {"accounts": [{"iam": {"users": [ + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZAAAAA", + "displayName": "test-user-1", + }, + "policies": [{"name": "test-user-policy-1"}, {"name": "test-user-policy-1-2"}], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZBBBB", + "displayName": "test-user-2", + }, + "policies": [{"name": "test-user-policy-2"}], + }, + { + "metadata": { + "id": "aws-iam-user|AIDA3K53E734T6EZCCCCC", + "displayName": "test-user-3", + }, + "policies": [{"name": "test-user-policy-3"}], + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide.graphql new file mode 100644 index 0000000..df5bdf4 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide.graphql @@ -0,0 +1,21 @@ +{ + aws { + accounts { + iam { + users { + metadata { + id + } + name + } + credentialReport { + contents { + user + passwordEnabled + mfaActive + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide.rego new file mode 100644 index 0000000..4fe23ee --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide.rego @@ -0,0 +1,32 @@ +package policy.aws.iam.user_mfa + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + + # Prepare a map from a user name to a user resource ID in Shisho Cloud + user_map := {user.name: user.metadata.id | + user := account.iam.users[_] + } + content := account.iam.credentialReport.contents[_] + subject := user_map[content.user] + + d := shisho.decision.aws.iam.user_mfa({ + "allowed": allowed(content), + "subject": subject, + "payload": shisho.decision.aws.iam.user_mfa_payload({ + "has_console_password": content.passwordEnabled, + "mfa_active": content.mfaActive, + }), + }) +} + +allowed(content) { + # if the console password is set, MFA should be active. + content.passwordEnabled == true + content.mfaActive == true +} else { + # if the console password is not set, MFA is not required. + content.passwordEnabled == false +} else := false diff --git a/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide_test.rego new file mode 100644 index 0000000..ad1c897 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/iam/user-mfa/decide_test.rego @@ -0,0 +1,101 @@ +package policy.aws.iam.user_mfa + +import data.shisho +import future.keywords + +test_whether_the_mfa_is_enabled_for_user_who_has_password if { + # check if the MFA is enabled for the user who has password + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"iam": { + "users": [ + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBB"}, + "name": "test-user", + }, + { + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBC"}, + "name": "test-user-2", + }, + ], + "credentialReport": { + "metadata": { + "id": "aws-iam-credential-report|77939277777", + "displayName": "Report for 779392177777", + }, + "contents": [ + { + "user": "", + "passwordEnabled": false, + "mfaActive": false, + }, + { + "user": "test-user", + "passwordEnabled": true, + "mfaActive": true, + }, + { + "user": "test-user-2", + "passwordEnabled": true, + "mfaActive": true, + }, + ], + }, + }}]}} + + # check if the MFA is enabled for the user who has password + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + {"iam": { + "users": [{ + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBD"}, + "name": "test-user", + }], + "credentialReport": { + "metadata": { + "id": "aws-iam-credential-report|77939277777", + "displayName": "Report for 779392177777", + }, + "contents": [ + { + "user": "", + "passwordEnabled": false, + "mfaActive": false, + }, + { + "user": "test-user", + "passwordEnabled": true, + "mfaActive": false, + }, + ], + }, + }}, + {"iam": { + "users": [{ + "metadata": {"id": "aws-iam-user|AIDA3K53E734T6EZBBBE"}, + "name": "test-user", + }], + "credentialReport": { + "metadata": { + "id": "aws-iam-credential-report|77939288888", + "displayName": "Report for 779392188888", + }, + "contents": [ + { + "user": "", + "passwordEnabled": false, + "mfaActive": false, + }, + { + "user": "test-user", + "passwordEnabled": true, + "mfaActive": false, + }, + ], + }, + }}, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/kms/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/kms/manifest.yaml new file mode 100644 index 0000000..3ca6b3d --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/kms/manifest.yaml @@ -0,0 +1,27 @@ +version: 0.1.0 + +id: "prebundle-aws-kms" +name: "Prebundle: Review AWS KMS posture" + +triggers: + schedule: + - cron: "*/10 * * * *" + +jobs: + - id: symmetric-cmk-rotation + name: Review rotation for customer created symmetric CMKs is enabled + decide: + rego: !include symmetric-cmk-rotation/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 symmetric-cmk-rotation/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide.graphql new file mode 100644 index 0000000..22c4ab7 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide.graphql @@ -0,0 +1,22 @@ +{ + aws { + accounts { + kms { + keys { + metadata { + id + displayName + } + keyManager + keySpec + keyRotationEnabled + + tags { + key + value + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide.rego new file mode 100644 index 0000000..a551973 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide.rego @@ -0,0 +1,32 @@ +package policy.aws.kms.symmetric_cmk_rotation + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + key := account.kms.keys[_] + + key.keyManager == "CUSTOMER" + key.keySpec == "SYMMETRIC_DEFAULT" + allowed := key.keyRotationEnabled == true + + d := shisho.decision.aws.kms.symmetric_cmk_rotation({ + "allowed": allow_if_excluded(allowed, key), + "subject": key.metadata.id, + "payload": shisho.decision.aws.kms.symmetric_cmk_rotation_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/kms/symmetric-cmk-rotation/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide_test.rego new file mode 100644 index 0000000..e45b608 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/kms/symmetric-cmk-rotation/decide_test.rego @@ -0,0 +1,65 @@ +package policy.aws.kms.symmetric_cmk_rotation + +import data.shisho +import future.keywords + +test_policy_rotation_for_symmetric_cmk_is_enabled if { + # check if the rotation for symmetric CMKs is enabled + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"kms": {"keys": [ + { + "metadata": { + "id": "aws-kms-key|ap-northeast-1|test-key-1", + "displayName": "test-key-1", + }, + "keyManager": "CUSTOMER", + "keySpec": "SYMMETRIC_DEFAULT", + "keyRotationEnabled": true, + }, + { + "metadata": { + "id": "aws-kms-key|ap-northeast-1|test-key-2", + "displayName": "test-key-2", + }, + "keyManager": "CUSTOMER", + "keySpec": "SYMMETRIC_DEFAULT", + "keyRotationEnabled": true, + }, + ]}}]}} + + # check if the rotation for symmetric CMKs is not enabled + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{"kms": {"keys": [ + { + "metadata": { + "id": "aws-kms-key|ap-northeast-1|test-key-1", + "displayName": "test-key-1", + }, + "keyManager": "CUSTOMER", + "keySpec": "RSA_2048", + "keyRotationEnabled": true, + }, + { + "metadata": { + "id": "aws-kms-key|ap-northeast-1|test-key-2", + "displayName": "test-key-2", + }, + "keyManager": "CUSTOMER", + "keySpec": "SYMMETRIC_DEFAULT", + "keyRotationEnabled": false, + }, + { + "metadata": { + "id": "aws-kms-key|ap-northeast-1|test-key-3", + "displayName": "test-key-3", + }, + "keyManager": "AWS", + "keySpec": "SYMMETRIC_DEFAULT", + "keyRotationEnabled": true, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide.rego new file mode 100644 index 0000000..a19b680 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.bucket_policy_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.bucket_policy_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.bucket_policy_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide_test.rego new file mode 100644 index 0000000..2906526 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/bucket-policy-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.bucket_policy_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_s3_bucket_policy if { + # check if there is a log metric filter and alarm for S3 bucket policy changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for S3 bucket policy changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide.rego new file mode 100644 index 0000000..ac283d9 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.cloudtrail_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.cloudtrail_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.cloudtrail_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide_test.rego new file mode 100644 index 0000000..65534cf --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cloudtrail-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.cloudtrail_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_cloudtrail_configuration_changes if { + # check if there is a log metric filter and alarm for CloudTrail configuration changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for CloudTrail configuration changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide.rego new file mode 100644 index 0000000..6a28622 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide.rego @@ -0,0 +1,86 @@ +package policy.aws.logmetric.cmk_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.cmk_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.cmk_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. + +patterns := ["{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide_test.rego new file mode 100644 index 0000000..d810827 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/cmk-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.cmk_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_customer_cmks if { + # check if there is a log metric filter and alarm for customer CMKs + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for customer CMKs + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide.rego new file mode 100644 index 0000000..47d7435 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.config_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.config_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.config_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide_test.rego new file mode 100644 index 0000000..0a4783f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/config-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.config_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_config_configuration_changes if { + # check if there is a log metric filter and alarm for Config configuration changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for Config configuration changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel) ||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide.rego new file mode 100644 index 0000000..bc2ce03 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.console_auth_failure + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.console_auth_failure({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.console_auth_failure_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide_test.rego new file mode 100644 index 0000000..ecc6a3e --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-auth-failure/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.console_auth_failure + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_console_authentication if { + # check if there is a log metric filter and alarm for console authentication + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for console authentication + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide.rego new file mode 100644 index 0000000..78646be --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.console_root_user_usage + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.console_root_user_usage({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.console_root_user_usage_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide_test.rego new file mode 100644 index 0000000..fa4e905 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-root-user-usage/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.console_root_user_usage + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_console_root_account_usage if { + # check if there is a log metric filter and alarm for console root account usage + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for console root account usage + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide.rego new file mode 100644 index 0000000..c65d3c3 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide.rego @@ -0,0 +1,88 @@ +package policy.aws.logmetric.console_signin_mfa + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.console_signin_mfa({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.console_signin_mfa_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := [ + "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") && ($.userIdentity.type = \"IAMUser\") && ($.responseElements.ConsoleLogin = \"Success\") }", +] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide_test.rego new file mode 100644 index 0000000..3b2d4a2 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/console-signin-mfa/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.console_signin_mfa + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_console_siginin_with_mfa if { + # check if there is a log metric filter and alarm for console signin without mfa + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") && ($.userIdentity.type = \"IAMUser\") && ($.responseElements.ConsoleLogin = \"Success\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for console signin without mfa + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide.rego new file mode 100644 index 0000000..67bf2de --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.iam_policy_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.iam_policy_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.iam_policy_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide_test.rego new file mode 100644 index 0000000..15b84bd --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/iam-policy-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.iam_policy_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_iam_policy_changes if { + # check if there is a log metric filter and alarm for IAM policy changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for IAM policy changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventNa me=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolic y)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=Del etePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersi on)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.event Name=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGr oupPolicy)||($.eventName=DetachGroupPolicy)}", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/logmetric/manifest.yaml new file mode 100644 index 0000000..44fabcf --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/manifest.yaml @@ -0,0 +1,265 @@ +version: 0.1.0 + +id: "prebundle-aws-logmetric" +name: "Prebundle: Review AWS Log Metric posture" + +triggers: + schedule: + - cron: "*/10 * * * *" + +jobs: + - id: config-changes + name: Review that a log metric filter and alarm exist for AWS Config configuration changes + decide: + rego: !include config-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 config-changes/decide.graphql + - id: cloudtrail-changes + name: Review that a log metric filter and alarm exist for CloudTrail configuration changes + decide: + rego: !include cloudtrail-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 cloudtrail-changes/decide.graphql + - id: console-signin-mfa + name: Review that a log metric filter and alarm exist for Management Console sign-in without MFA + decide: + rego: !include console-signin-mfa/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 console-signin-mfa/decide.graphql + - id: vpc-changes + name: Review that a log metric filter and alarm exist for VPC changes + decide: + rego: !include vpc-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 vpc-changes/decide.graphql + - id: security-group-changes + name: Review that a log metric filter and alarm exist for security group changes + decide: + rego: !include security-group-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 security-group-changes/decide.graphql + - id: console-auth-failure + name: Review that a log metric filter and alarm exist for AWS Management Console authentication failures + decide: + rego: !include console-auth-failure/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 console-auth-failure/decide.graphql + - id: console-root-user-usage + name: Review that a log metric filter and alarm exist for usage of the root user + decide: + rego: !include console-root-user-usage/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 console-root-user-usage/decide.graphql + - id: organizations-changes + name: Review that a log metric filter and alarm exist for AWS Organizations changes + decide: + rego: !include organizations-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 organizations-changes/decide.graphql + - id: nacl-changes + name: Review that a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) + decide: + rego: !include nacl-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 nacl-changes/decide.graphql + - id: route-table-changes + name: Review that a log metric filter and alarm exist for route table changes + decide: + rego: !include route-table-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 route-table-changes/decide.graphql + - id: network-gateway-changes + name: Review that a log metric filter and alarm exist for changes to network gateways + decide: + rego: !include network-gateway-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 network-gateway-changes/decide.graphql + - id: unauthorized-api-calls + name: Review that a log metric filter and alarm exist for unauthorized API calls + decide: + rego: !include unauthorized-api-calls/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 unauthorized-api-calls/decide.graphql + - id: iam-policy-changes + name: Review that a log metric filter and alarm exist for IAM policy changes + decide: + rego: !include iam-policy-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 iam-policy-changes/decide.graphql + - id: bucket-policy-changes + name: Review that a log metric filter and alarm exist for S3 bucket policy changes + decide: + rego: !include bucket-policy-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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-policy-changes/decide.graphql + - id: cmk-changes + name: Review that a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs + decide: + rego: !include cmk-changes/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 cmk-changes/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide.rego new file mode 100644 index 0000000..3b08848 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.nacl_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.nacl_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.nacl_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide_test.rego new file mode 100644 index 0000000..25b794e --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/nacl-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.nacl_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_nacl if { + # check if there is a log metric filter and alarm for NACL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for NACL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide.rego new file mode 100644 index 0000000..f035050 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.network_gateway_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.network_gateway_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.network_gateway_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide_test.rego new file mode 100644 index 0000000..e22050e --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/network-gateway-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.network_gateway_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_network_gateways if { + # check if there is a log metric filter and alarm for network gateways + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for network gateways + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide.rego new file mode 100644 index 0000000..1f7d552 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.organizations_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.organizations_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.organizations_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide_test.rego new file mode 100644 index 0000000..5d2c5db --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/organizations-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.organizations_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_organizations if { + # check if there is a log metric filter and alarm for AWS Organizations changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for AWS Organizations changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide.rego new file mode 100644 index 0000000..d85b5e5 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.route_table_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.route_table_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.route_table_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide_test.rego new file mode 100644 index 0000000..ba71891 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/route-table-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.route_table_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_route_table if { + # check if there is a log metric filter and alarm for route table changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for route table changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide.rego new file mode 100644 index 0000000..9b5853a --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.security_group_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.security_group_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.security_group_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide_test.rego new file mode 100644 index 0000000..9a0a103 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/security-group-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.security_group_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_security_group_changes if { + # check if there is a log metric filter and alarm for security group changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for security group changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide.rego new file mode 100644 index 0000000..535dae0 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.unauthorized_api_calls + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.unauthorized_api_calls({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.unauthorized_api_calls_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide_test.rego new file mode 100644 index 0000000..1f94ba4 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/unauthorized-api-calls/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.unauthorized_api_calls + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_unauthorized_api_calls if { + # check if there is a log metric filter and alarm for unauthorized api calls + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for unauthorized api calls + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.errorCode = *UnauthorizedOperation) || ($.errorCode = AccessDenied*) || ($.sourceIPAddress!=delivery.logs.amazonaws.com) || ($.eventName!=HeadBucket) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide.graphql new file mode 100644 index 0000000..b0d471f --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide.graphql @@ -0,0 +1,89 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + isMultiRegionTrail + cloudWatchLogGroup { + metadata { + id + displayName + } + arn + metricFilters { + pattern + metricTransformations { + name + } + } + } + status { + isLogging + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + includeManagementEvents + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + equals + } + } + } + } + } + cloudWatch { + alarms { + ... on AWSCloudWatchMetricAlarm { + metadata { + id + displayName + } + metricName + alarmActions { + ... on AWSCloudWatchAlarmActionNotification { + __typename + arn + topic { + ... on AWSSNSTopicFifo { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + ... on AWSSNSTopicStandard { + metadata { + id + displayName + } + arn + subscriptions { + arn + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide.rego new file mode 100644 index 0000000..e9ac56c --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide.rego @@ -0,0 +1,85 @@ +package policy.aws.logmetric.vpc_changes + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + paths := cis_based_notification_paths(account) + + d := shisho.decision.aws.logmetric.vpc_changes({ + "allowed": count(paths) > 0, + "subject": account.metadata.id, + "payload": shisho.decision.aws.logmetric.vpc_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The CloudWatch LogMetric pattern to match the events to notify. +# This is exactly same as CIS AWS Foundations Benchmark v1.5.0 defines. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }"] + +# Find a path that implements the notification from CloudTrail to a SNS topic as described in CIS AWS Foundations Benchmark v1.5.0. +cis_based_notification_paths(account) := x { + x := [path | + # (1) Find a trail that cover all regions, log events actively, and include management events.... + trail := account.cloudTrail.trails[_] + trail.isMultiRegionTrail == true + trail.status.isLogging == true + all_management_rw_events_recorded(trail.eventSelectors) + + # ... and a filter that matches the CIS pattern... + filter := trail.cloudWatchLogGroup.metricFilters[_] + filter.pattern == patterns[_] + + # ... and a metric on the filter. + metric := filter.metricTransformations[_] + + # (2) Find an alarm that uses the metric. + alarm := account.cloudWatch.alarms[_] + alarm.metricName == metric.name + + # (3) Find a SNS topic that the alarm notifies and has at least one subscription. + sns_topics := sns_topic_to_be_notified(alarm.alarmActions) + topic := sns_topics[_] + count(topic.subscriptions) > 0 + + # Now (1) - (3) form a path from CloudTrail to a SNS topic (and somewhere else connected to the topic). + path := { + "trail_name": trail.metadata.displayName, + "metric_name": metric.name, + "alarm_name:": alarm.metadata.displayName, + "sns_topic_arn": topic.arn, + } + ] +} else = [] + +# Check whether all management events are recorded or not. +# This function cannot cover advanced event selectors as CIS AWS Foundations Benchmark v1.5.0 does not allow them as of now. +all_management_rw_events_recorded(selectors) { + selector = selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + selector.includeManagementEvents == true + selector.readWriteType == "ALL" +} else { + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + contains_field_eq_selector(selector.fieldSelectors, "eventCategory", "Management") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "true") + not contains_field_eq_selector(selector.fieldSelectors, "readOnly", "false") +} else = false + +contains_field_eq_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false + +sns_topic_to_be_notified(actions) := x { + x := [action.topic | + action := actions[_] + action.__typename == "AWSCloudWatchAlarmActionNotification" + startswith(action.topic.arn, "arn:aws:sns:") + ] +} else = [] diff --git a/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide_test.rego new file mode 100644 index 0000000..a53afa6 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/logmetric/vpc-changes/decide_test.rego @@ -0,0 +1,594 @@ +package policy.aws.logmetric.vpc_changes + +import data.shisho +import future.keywords + +test_whether_log_metric_filter_and_alarm_exist_for_vpc if { + # check if there is a log metric filter and alarm for VPC changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779397777777-87777777", + "displayName": "aws-cloudtrail-logs-779397777777-87777777", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-cloudtrail-logs-779397777777-87777777:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-cloudtrail-logs-779398888888-88888888", + "displayName": "aws-cloudtrail-logs-779398888888-88888888", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-cloudtrail-logs-779398888888-88888888:*", + "metricFilters": [], + }, + "status": {"isLogging": true}, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "equals": ["Data"], + }, + { + "field": "resources.type", + "equals": ["AWS::S3::Object"], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "equals": ["Management"], + }], + }, + ], + }, + ]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + ]}} + + # check if there is not a log metric filter and alarm for VPC changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 8 with input as {"aws": {"accounts": [ + { + "metadata": { + "id": "aws-account|779397777777", + "displayName": "779397777777", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": false, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779397777777:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-2"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-3", + "displayName": "test-alarm-3", + }, + "name": "test-alarm-3", + "metricName": "test-metric-name-2", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-1.fifo", + "displayName": "test-topic-1.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779397777777:test-topic-1.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779397777777:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779397777777"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779398888888", + "displayName": "779398888888", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779398888888:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": false}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779398888888:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779398888888:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779398888888"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779399999999", + "displayName": "779399999999", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779399999999:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": false, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779399999999:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779399999999:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779399999999"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779390000000", + "displayName": "779390000000", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779390000000:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "READ_ONLY", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779390000000:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779390000000:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779390000000"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779391111111", + "displayName": "779391111111", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779391111111:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779391111111:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779391111111:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779391111111"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779392222222", + "displayName": "779392222222", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779392222222:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-4"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-2.fifo", + "displayName": "test-topic-2.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779392222222:test-topic-2.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779392222222:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779392222222"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779394444444", + "displayName": "779394444444", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779394444444:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "ERROR", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779394444444:test-topic-3.fifo", + "subscriptions": [{"arn": "arn:aws:sns:ap-northeast-1:779394444444:aws-controltower-SecurityNotifications:1e9d59ad-edb8-4af7-a566-779394444444"}], + }, + }], + }]}, + }, + { + "metadata": { + "id": "aws-account|779395555555", + "displayName": "779395555555", + }, + "cloudTrail": {"trails": [{ + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|aws-controltower-BaselineCloudTrail", + "displayName": "aws-controltower-BaselineCloudTrail", + }, + "isMultiRegionTrail": true, + "cloudWatchLogGroup": { + "metadata": { + "id": "aws-cloudwatch-log-group|ap-northeast-1|aws-controltower/CloudTrailLogs", + "displayName": "aws-controltower/CloudTrailLogs", + }, + "arn": "arn:aws:logs:ap-northeast-1:779395555555:log-group:aws-controltower/CloudTrailLogs:*", + "metricFilters": [{ + "pattern": "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }", + "metricTransformations": [{"name": "test-metric-name-3"}], + }], + }, + "status": {"isLogging": true}, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "includeManagementEvents": true, + "readWriteType": "ALL", + }], + }]}, + "cloudWatch": {"alarms": [{ + "metadata": { + "id": "aws-cloudwatch-alarm|ap-northeast-1|test-alarm-4", + "displayName": "test-alarm-4", + }, + "name": "test-alarm-4", + "metricName": "test-metric-name-3", + "alarmActions": [{ + "__typename": "AWSCloudWatchAlarmActionNotification", + "topic": { + "metadata": { + "id": "aws-sns-topic|ap-northeast-1|test-topic-3.fifo", + "displayName": "test-topic-3.fifo", + }, + "arn": "arn:aws:sns:ap-northeast-1:779395555555:test-topic-3.fifo", + "subscriptions": [], + }, + }], + }]}, + }, + ]}} +} 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 091ae6d..2f4da6e 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/networking/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/manifest.yaml @@ -9,7 +9,7 @@ triggers: jobs: - id: acl-ingress - name: Review the flow log configuration + name: Review the ACL configuration (on inbound traffic from 0.0.0.0/0 to tcp/22 + 3389) decide: rego: !include acl-ingress/decide.rego with: @@ -26,7 +26,7 @@ jobs: input: schema: !include acl-ingress/decide.graphql - id: sg-ingress-v4 - name: Review the security group configuration + name: Review the security group configuration (on inbound traffic from 0.0.0.0/0 to tcp/22 + 3389) decide: rego: !include sg-ingress-v4/decide.rego with: @@ -43,7 +43,7 @@ jobs: input: schema: !include sg-ingress-v4/decide.graphql - id: sg-ingress-v6 - name: Review the default security group configuration + name: Review the default security group configuration n (on inbound traffic from ::/0 to tcp/22 + 3389) decide: rego: !include sg-ingress-v6/decide.rego with: @@ -59,3 +59,37 @@ jobs: values: [] input: schema: !include sg-ingress-v6/decide.graphql + - id: vpc-flow-logging + name: Review AWS VPC flow logging is enabled + decide: + rego: !include vpc-flow-logging/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 vpc-flow-logging/decide.graphql + - id: sg-baseline + name: Review the default security group restricts all traffic + decide: + rego: !include sg-baseline/decide.rego + with: + resource_exceptions: + type: resource_exception + 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-baseline/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide.graphql new file mode 100644 index 0000000..cc108ac --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide.graphql @@ -0,0 +1,31 @@ +{ + aws { + accounts { + network { + vpcs { + securityGroups { + metadata { + id + displayName + } + name + ipPermissionsIngress { + ipProtocol + fromPort + toPort + } + ipPermissionsEgress { + ipProtocol + fromPort + toPort + } + tags { + key + value + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide.rego new file mode 100644 index 0000000..b98b39d --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide.rego @@ -0,0 +1,52 @@ +package policy.aws.networking.sg_baseline + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + vpc := account.network.vpcs[_] + g := vpc.securityGroups[_] + + g.name == "default" + + ip_permissions_ingress := ip_permissions(g.ipPermissionsIngress) + ip_permissions_egress := ip_permissions(g.ipPermissionsEgress) + + d := shisho.decision.aws.networking.sg_baseline({ + "allowed": allow_if_excluded(no_ip_permissions(ip_permissions_ingress, ip_permissions_egress), g), + "subject": g.metadata.id, + "payload": shisho.decision.aws.networking.sg_baseline_payload({ + "ip_permissions_ingress": ip_permissions_ingress, + "ip_permissions_egress": ip_permissions_egress, + }), + }) +} + +no_ip_permissions(ip_permissions_ingress, ip_permissions_egress) { + count(ip_permissions_ingress) == 0 + count(ip_permissions_egress) == 0 +} else = false + +ip_permissions(group_ip_permissions) = x { + x := [{ + "ip_protocol": group_ip_permission.ipProtocol, + "from_port": group_ip_permission.fromPort, + "to_port": group_ip_permission.toPort, + } | + group_ip_permission = group_ip_permissions[_] + ] +} 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/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide_test.rego new file mode 100644 index 0000000..816eafe --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/sg-baseline/decide_test.rego @@ -0,0 +1,95 @@ +package policy.aws.networking.sg_baseline + +import data.shisho +import future.keywords + +test_whether_the_ip_permissions_for_default_security_groups_are_allowed if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"network": {"vpcs": [ + {"securityGroups": [{ + "metadata": { + "id": "aws-vpc-secuirty-group|eu-west-3|vpc-06bcfc98ac7777777|sg-08aefa50d1f7777777", + "displayName": "sg-08aefa50d1f7777777", + }, + "name": "default", + "ipPermissionsIngress": [], + "ipPermissionsEgress": [], + "tags": [], + }]}, + {"securityGroups": [{ + "metadata": { + "id": "aws-vpc-secuirty-group|eu-central-1|vpc-06bcfc98ac7777778|sg-08aefa50d1f7777778", + "displayName": "sg-08aefa50d1f7777778", + }, + "name": "default", + "ipPermissionsIngress": [], + "ipPermissionsEgress": [], + "tags": [], + }]}, + ]}}]}} +} + +test_whether_the_ip_permissions_for_default_security_groups_are_denied if { + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"network": {"vpcs": [ + {"securityGroups": [{ + "metadata": { + "id": "aws-vpc-secuirty-group|eu-west-3|vpc-06bcfc98ac7777777|sg-08aefa50d1f7777777", + "displayName": "sg-08aefa50d1f7777777", + }, + "name": "default", + "ipPermissionsIngress": [{ + "ipProtocol": "all", + "fromPort": 0, + "toPort": 0, + }], + "ipPermissionsEgress": [{ + "ipProtocol": "all", + "fromPort": 0, + "toPort": 0, + }], + "tags": [], + }]}, + {"securityGroups": [{ + "metadata": { + "id": "aws-vpc-secuirty-group|eu-central-1|vpc-06bcfc98ac7777778|sg-08aefa50d1f7777778", + "displayName": "sg-08aefa50d1f7777778", + }, + "name": "default", + "ipPermissionsIngress": [{ + "ipProtocol": "all", + "fromPort": 0, + "toPort": 0, + }], + "ipPermissionsEgress": [{ + "ipProtocol": "all", + "fromPort": 0, + "toPort": 0, + }], + "tags": [], + }]}, + {"securityGroups": [{ + "metadata": { + "id": "aws-vpc-secuirty-group|eu-central-1|vpc-06bcfc98ac7777779|sg-08aefa50d1f7777779", + "displayName": "sg-08aefa50d1f7777779", + }, + "name": "default", + "ipPermissionsIngress": [{ + "ipProtocol": "all", + "fromPort": 0, + "toPort": 0, + }], + "ipPermissionsEgress": [{ + "ipProtocol": "all", + "fromPort": 0, + "toPort": 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/networking/vpc-flow-logging/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/networking/vpc-flow-logging/decide.graphql new file mode 100644 index 0000000..bcb4dec --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/vpc-flow-logging/decide.graphql @@ -0,0 +1,22 @@ +{ + aws { + accounts { + network { + vpcs { + metadata { + id + displayName + } + flowLogs { + id + } + + tags { + key + value + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/aws-v1.5.0/networking/vpc-flow-logging/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/vpc-flow-logging/decide.rego new file mode 100644 index 0000000..7d70ad4 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/vpc-flow-logging/decide.rego @@ -0,0 +1,30 @@ +package policy.aws.networking.vpc_flow_logging + +import data.shisho + +decisions[d] { + account := input.aws.accounts[_] + vpc := account.network.vpcs[_] + + allowed := count(vpc.flowLogs) > 0 + + d := shisho.decision.aws.networking.vpc_flow_logging({ + "allowed": allow_if_excluded(allowed, vpc), + "subject": vpc.metadata.id, + "payload": shisho.decision.aws.networking.vpc_flow_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/networking/vpc-flow-logging/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/networking/vpc-flow-logging/decide_test.rego new file mode 100644 index 0000000..3b755d5 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/networking/vpc-flow-logging/decide_test.rego @@ -0,0 +1,51 @@ +package policy.aws.networking.vpc_flow_logging + +import data.shisho +import future.keywords + +test_flow_logging_is_enabled_for_vpcs if { + # check if flow logging is enabled for VPCs + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"network": {"vpcs": [ + { + "metadata": { + "id": "aws-vpc|ap-northeast-1|vpc-0fb9667dee2b88888", + "displayName": "vpc-0fb9667dee2b88888", + }, + "flowLogs": [ + {"id": "fl-061ce974280e965b2"}, + {"id": "fl-05a3f35a72edeb158"}, + ], + }, + { + "metadata": { + "id": "aws-vpc|ap-northeast-1|vpc-06dc8a2abafd99999", + "displayName": "vpc-06dc8a2abafd99999", + }, + "flowLogs": [{"id": "fl-0791e8000f7ea0a3a"}], + }, + ]}}]}} + + # check if flow logging is not enabled for VPCs + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"aws": {"accounts": [{"network": {"vpcs": [ + { + "metadata": { + "id": "aws-vpc|ap-northeast-1|vpc-0fc5a1285f1377777", + "displayName": "vpc-0fc5a1285f1377777", + }, + "flowLogs": [], + }, + { + "metadata": { + "id": "aws-vpc|ap-northeast-2|vpc-0f31afb4562400000", + "displayName": "vpc-0f31afb4562400000", + }, + "flowLogs": [], + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide.graphql new file mode 100644 index 0000000..f0b0302 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide.graphql @@ -0,0 +1,46 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + dataResources { + type + values + } + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + endsWith + equals + notEndsWith + notEquals + notStartsWith + startsWith + } + } + } + + tags { + key + value + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide.rego new file mode 100644 index 0000000..4e4b014 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide.rego @@ -0,0 +1,80 @@ +package policy.aws.s3.bucket_read_trail + +import data.shisho + +# Whether to allow the account CloudTrail configurations S3 object logging is limited to some buckets/objects. +allow_limited_logging := data.params.allow_limited_logging { + data.params != null + [true, false][_] == data.params.allow_limited_logging +} else := true + +decisions[d] { + account := input.aws.accounts[_] + allowed := has_trail_with_s3_read(account.cloudTrail.trails) + + d := shisho.decision.aws.s3.bucket_read_trail({ + "allowed": allowed, + "subject": account.metadata.id, + "payload": shisho.decision.aws.s3.bucket_read_trail_payload({"enabled": allowed}), + }) +} + +has_trail_with_s3_read(trails) { + trail := trails[_] + has_data_event_selector(trail) +} else := false + +has_data_event_selector(trail) { + has_basic_selector_with_s3_read(trail.eventSelectors) +} else { + has_advanced_selector_with_s3_read(trail.eventSelectors) +} else = false + +has_basic_selector_with_s3_read(selectors) { + # Find a selector that .... + selector := selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + + # logs all read events ... + allowed_types := ["ALL", "READ_ONLY"] + allowed_types[_] == selector.readWriteType + + # ... for S3 objects. + resource := selector.dataResources[_] + resource.type == "AWS::S3::Object" + records_enough(resource) +} else = false + +# Cnfirm the basic selector dataResources covers the enough range of S3 buckets. +# NOTE: +# - `values` can include more limited number of values, e.g. `arn:aws:s3:::mybucket/*`. +# - specifying `"arn:aws:s3"` in `values` selects all S3 objects in all buckets for logging targets. +# - if your organization chooses to limit the scope of logging to specific buckets, you can comment out the condition on `values` in this policy code. +records_enough(resource) { + # if `allow_limited_logging` is true, then `values` don't have to cover all S3 buckets. + allow_limited_logging +} else { + # if `allow_limited_logging` is false, then `values` must cover all S3 buckets. + value := resource.values[_] + value == "arn:aws:s3" +} + +has_advanced_selector_with_s3_read(selectors) { + # Find an advanced selector that .... + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + # logs all read events ... + contains_field_selector(selector.fieldSelectors, "eventCategory", "Data") + not contains_field_selector(selector.fieldSelectors, "readOnly", "false") + + # ... for S3 objects. + contains_field_selector(selector.fieldSelectors, "resources.type", "AWS::S3::Object") +} else = false + +contains_field_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide_test.rego new file mode 100644 index 0000000..c57ac44 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-read-trail/decide_test.rego @@ -0,0 +1,182 @@ +package policy.aws.s3.bucket_read_trail + +import data.shisho +import future.keywords + +test_whether_cloudtrail_logs_read_data_events_of_s3_buckets if { + # check if the CloudTrail logs read data events of S3 buckets + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "dataResources": [{ + "type": "AWS::S3::Object", + "values": ["arn:aws:s3"], + }], + "readWriteType": "READ_ONLY", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "dataResources": [{ + "type": "AWS::S3::Object", + "values": ["arn:aws:s3"], + }], + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "endsWith": [], + "equals": ["Data"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "resources.type", + "endsWith": [], + "equals": ["AWS::S3::Object"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "readOnly", + "endsWith": [], + "equals": ["true"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "endsWith": [], + "equals": ["Management"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }], + }, + ], + }, + ]}, + }]}} + + # check if the CloudTrail does not log read data events of S3 buckets + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "dataResources": [{ + "type": "AWS::S3::Object", + "values": ["arn:aws:s3"], + }], + "readWriteType": "WRITE_ONLY", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "endsWith": [], + "equals": ["Data"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "resources.type", + "endsWith": [], + "equals": ["AWS::S3::Object"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "readOnly", + "endsWith": [], + "equals": ["false"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "endsWith": [], + "equals": ["Management"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }], + }, + ], + }, + ]}, + }]}} +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide.graphql new file mode 100644 index 0000000..f0b0302 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide.graphql @@ -0,0 +1,46 @@ +{ + aws { + accounts { + metadata { + id + displayName + } + cloudTrail { + trails { + metadata { + id + displayName + } + eventSelectors { + ... on AWSCloudTrailBasicEventSelector { + __typename + dataResources { + type + values + } + readWriteType + } + ... on AWSCloudTrailAdvancedEventSelector { + __typename + name + fieldSelectors { + field + endsWith + equals + notEndsWith + notEquals + notStartsWith + startsWith + } + } + } + + tags { + key + value + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide.rego new file mode 100644 index 0000000..12ddf92 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide.rego @@ -0,0 +1,80 @@ +package policy.aws.s3.bucket_write_trail + +import data.shisho + +# Whether to allow the account CloudTrail configurations S3 object logging is limited to some buckets/objects. +allow_limited_logging := data.params.allow_limited_logging { + data.params != null + [true, false][_] == data.params.allow_limited_logging +} else := true + +decisions[d] { + account := input.aws.accounts[_] + allowed := has_trail_with_s3_write(account.cloudTrail.trails) + + d := shisho.decision.aws.s3.bucket_write_trail({ + "allowed": allowed, + "subject": account.metadata.id, + "payload": shisho.decision.aws.s3.bucket_write_trail_payload({"enabled": allowed}), + }) +} + +has_trail_with_s3_write(trails) { + trail := trails[_] + has_data_event_selector(trail) +} else = false + +has_data_event_selector(trail) { + has_basic_selector_with_s3_write(trail.eventSelectors) +} else { + has_advanced_selector_with_s3_write(trail.eventSelectors) +} else = false + +has_basic_selector_with_s3_write(selectors) { + # Find a selector that .... + selector := selectors[_] + selector.__typename == "AWSCloudTrailBasicEventSelector" + + # logs all write events ... + allowed_types := ["ALL", "WRITE_ONLY"] + allowed_types[_] == selector.readWriteType + + # ... for S3 objects. + resource := selector.dataResources[_] + resource.type == "AWS::S3::Object" + records_enough(resource) +} else = false + +# Cnfirm the basic selector dataResources covers the enough range of S3 buckets. +# NOTE: +# - `values` can include more limited number of values, e.g. `arn:aws:s3:::mybucket/*`. +# - specifying `"arn:aws:s3"` in `values` selects all S3 objects in all buckets for logging targets. +# - if your organization chooses to limit the scope of logging to specific buckets, you can comment out the condition on `values` in this policy code. +records_enough(resource) { + # if `allow_limited_logging` is true, then `values` don't have to cover all S3 buckets. + allow_limited_logging +} else { + # if `allow_limited_logging` is false, then `values` must cover all S3 buckets. + value := resource.values[_] + value == "arn:aws:s3" +} + +has_advanced_selector_with_s3_write(selectors) { + # Find an advanced selector that .... + selector = selectors[_] + selector.__typename == "AWSCloudTrailAdvancedEventSelector" + + # logs all write events ... + contains_field_selector(selector.fieldSelectors, "eventCategory", "Data") + not contains_field_selector(selector.fieldSelectors, "readOnly", "true") + + # ... for S3 objects. + contains_field_selector(selector.fieldSelectors, "resources.type", "AWS::S3::Object") +} else = false + +contains_field_selector(field_selectors, field, value) { + field_selector := field_selectors[_] + field_selector.field == field + eq := field_selector.equals[_] + eq == value +} else = false diff --git a/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide_test.rego new file mode 100644 index 0000000..89d1b66 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/bucket-write-trail/decide_test.rego @@ -0,0 +1,182 @@ +package policy.aws.s3.bucket_write_trail + +import data.shisho +import future.keywords + +test_whether_cloudtrail_logs_write_data_events_of_s3_buckets if { + # check if the CloudTrail logs write data events of S3 buckets + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "dataResources": [{ + "type": "AWS::S3::Object", + "values": ["arn:aws:s3"], + }], + "readWriteType": "WRITE_ONLY", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-3", + "displayName": "test-trail-3", + }, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "dataResources": [{ + "type": "AWS::S3::Object", + "values": ["arn:aws:s3"], + }], + "readWriteType": "ALL", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "endsWith": [], + "equals": ["Data"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "resources.type", + "endsWith": [], + "equals": ["AWS::S3::Object"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "readOnly", + "endsWith": [], + "equals": ["false"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "endsWith": [], + "equals": ["Management"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }], + }, + ], + }, + ]}, + }]}} + + # check if the CloudTrail does not log write data events of S3 buckets + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": { + "id": "aws-account|779392177777", + "displayName": "779392177777", + }, + "cloudTrail": {"trails": [ + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-1", + "displayName": "test-trail-1", + }, + "eventSelectors": [{ + "__typename": "AWSCloudTrailBasicEventSelector", + "dataResources": [{ + "type": "AWS::S3::Object", + "values": ["arn:aws:s3"], + }], + "readWriteType": "READ_ONLY", + }], + }, + { + "metadata": { + "id": "aws-cloudtrail-trail|ap-northeast-1|test-trail-2", + "displayName": "test-trail-2", + }, + "eventSelectors": [ + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "", + "fieldSelectors": [ + { + "field": "eventCategory", + "endsWith": [], + "equals": ["Data"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "resources.type", + "endsWith": [], + "equals": ["AWS::S3::Object"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + { + "field": "readOnly", + "endsWith": [], + "equals": ["true"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }, + ], + }, + { + "__typename": "AWSCloudTrailAdvancedEventSelector", + "name": "Management events selector", + "fieldSelectors": [{ + "field": "eventCategory", + "endsWith": [], + "equals": ["Management"], + "notEndsWith": [], + "notEquals": [], + "notStartsWith": [], + "startsWith": [], + }], + }, + ], + }, + ]}, + }]}} +} 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 7498323..aabb7a9 100644 --- a/workflows/cis-benchmark/aws-v1.5.0/s3/manifest.yaml +++ b/workflows/cis-benchmark/aws-v1.5.0/s3/manifest.yaml @@ -60,7 +60,7 @@ jobs: input: schema: !include bucket-mfa-delete/decide.graphql - id: bucket-public-access - name: Review the public accessibility of buckets + name: Review the public access block feature configurations decide: rego: !include bucket-public-access/decide.rego with: @@ -93,3 +93,37 @@ jobs: values: [] input: schema: !include bucket-transport/decide.graphql + - id: bucket-write-trail + name: Review CloudTrail trails are logging S3 bucket data write events + decide: + rego: !include bucket-write-trail/decide.rego + with: + resource_exceptions: + type: resource_exception + 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-write-trail/decide.graphql + - id: bucket-read-trail + name: Review CloudTrail trails are logging S3 bucket read events + decide: + rego: !include bucket-read-trail/decide.rego + with: + resource_exceptions: + type: resource_exception + 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-read-trail/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/securityhub/manifest.yaml b/workflows/cis-benchmark/aws-v1.5.0/securityhub/manifest.yaml new file mode 100644 index 0000000..0bfee27 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/securityhub/manifest.yaml @@ -0,0 +1,27 @@ +version: 0.1.0 + +id: "prebundle-aws-securityhub" +name: "Prebundle: Review AWS Security Hub posture" + +triggers: + schedule: + - cron: "*/10 * * * *" + +jobs: + - id: usage + name: Review AWS Security Hub is enabled + decide: + rego: !include usage/decide.rego + with: + resource_exceptions: + type: resource_exception + 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 usage/decide.graphql diff --git a/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide.graphql b/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide.graphql new file mode 100644 index 0000000..014b950 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide.graphql @@ -0,0 +1,16 @@ +{ + aws { + accounts { + metadata { + id + } + + securityHub { + subscriptions { + region + subscribed + } + } + } + } +} diff --git a/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide.rego b/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide.rego new file mode 100644 index 0000000..ff0b009 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide.rego @@ -0,0 +1,43 @@ +package policy.aws.securityhub.usage + +import data.shisho +import future.keywords.every + +# Add new regions to the list below if you want to ignore some regions in this policies +region_exceptions := (shisho.thirdparty.aws.opt_in_regions | shisho.thirdparty.aws.china_regions) | shisho.thirdparty.aws.gov_regions + +decisions[d] { + account := input.aws.accounts[_] + + enabled_regions := [s.region | + s := account.securityHub.subscriptions[_] + s.subscribed + ] + d := shisho.decision.aws.securityhub.usage({ + "allowed": enabled_in_all_regions(account.securityHub.subscriptions), + "subject": account.metadata.id, + "payload": shisho.decision.aws.securityhub.usage_payload({ + "enabled_regions": enabled_regions, + "missing_regions": [r | + r := shisho.thirdparty.aws.regions[_] + not is_covered(r, enabled_regions) + ], + }), + }) +} + +enabled_in_all_regions(subscriptions) { + covered_regions := {s.region | + s := subscriptions[_] + s.subscribed + } + every r in shisho.thirdparty.aws.regions { + is_covered(r, covered_regions) + } +} else := false + +is_covered(r, covered_regions) { + r in covered_regions +} else { + r in region_exceptions +} else := false diff --git a/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide_test.rego b/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide_test.rego new file mode 100644 index 0000000..4dd2976 --- /dev/null +++ b/workflows/cis-benchmark/aws-v1.5.0/securityhub/usage/decide_test.rego @@ -0,0 +1,44 @@ +package policy.aws.securityhub.usage + +import data.shisho +import future.keywords + +test_policy_security_hub_is_subscribed if { + # check if Security Hub is subscribed in all regions + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": {"id": "aws-account|779392187777"}, + "securityHub": {"subscriptions": [ + { + "region": "ap-northeast-2", + "subscribed": true, + }, + { + "region": "ap-northeast-1", + "subscribed": true, + }, + ]}, + }]}} + with data.shisho.thirdparty.aws.regions as {"ap-northeast-1", "ap-northeast-2"} + + # check if Security Hub is not subscribed + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 1 with input as {"aws": {"accounts": [{ + "metadata": {"id": "aws-account|779392187777"}, + "securityHub": {"subscriptions": [ + { + "region": "ap-northeast-2", + "subscribed": true, + }, + { + "region": "ap-northeast-1", + "subscribed": false, + }, + ]}, + }]}} + with data.shisho.thirdparty.aws.regions as {"ap-northeast-1", "ap-northeast-2"} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/bigquery/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/bigquery/manifest.yaml index 1b9692f..abb461c 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/bigquery/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/bigquery/manifest.yaml @@ -23,7 +23,7 @@ jobs: - id: dataset-encryption-cmek name: Review the encryption of tables decide: - rego: !include table-encryption-cmek/decide.rego + rego: !include dataset-encryption-cmek/decide.rego with: resource_exceptions: type: resource_exception @@ -31,11 +31,11 @@ jobs: description: A special list of resource exceptions values: [] input: - schema: !include table-encryption-cmek/decide.graphql + schema: !include dataset-encryption-cmek/decide.graphql - id: table-encryption-cmek name: Review the encryption of datasets decide: - rego: !include dataset-encryption-cmek/decide.rego + rego: !include table-encryption-cmek/decide.rego with: resource_exceptions: type: resource_exception @@ -43,4 +43,4 @@ jobs: description: A special list of resource exceptions values: [] input: - schema: !include dataset-encryption-cmek/decide.graphql + schema: !include table-encryption-cmek/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/disk-encryption-key/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/disk-encryption-key/decide.rego index ae15757..a689bd3 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/disk-encryption-key/decide.rego +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/disk-encryption-key/decide.rego @@ -17,36 +17,34 @@ decisions[d] { non_csek_keys(disks) := x { x := [{ "target_disk": disk.deviceName, - "key_name": disk.diskEncryptionKey.kmsKeyName, + "key_name": key_name, "key_type": key_type, } | disk := disks[_] + key_name := encryption_key_name(disk.diskEncryptionKey) key_type := encryption_key_type(disk.diskEncryptionKey) key_type != shisho.decision.googlecloud.compute.ENCRYPTION_KEY_TYPE_ENCRYPTION_KEY_TYPE_CUSTOMER_SUPPLIED ] } else := [] +encryption_key_name(key) = key.kmsKeyName { + key != null + key.kmsKeyName != "" +} else = "" + # key.kmsKeyName: The name of the encryption key that is stored in Google Cloud KMS -# key.kmsKeyServiceAccount: /The service account being used for the encryption request for the given KMS key +# key.kmsKeyServiceAccount: The service account being used for the encryption request for the given KMS key # key.sha256: The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource -encryption_key_type(key) = shisho.decision.googlecloud.compute.ENCRYPTION_KEY_TYPE_NONE { +encryption_key_type(key) = shisho.decision.googlecloud.compute.ENCRYPTION_KEY_TYPE_ENCRYPTION_KEY_TYPE_GOOGLE_MANAGED { + # This case means that the disk is encrypted by Google-managed encryption keys. key == null } else = shisho.decision.googlecloud.compute.ENCRYPTION_KEY_TYPE_NONE { - # If this is empty, the encryption key is disabled + # This case also means that the disk is encrypted by Google-managed encryption keys. key.kmsKeyName == "" -} else = shisho.decision.googlecloud.compute.ENCRYPTION_KEY_TYPE_ENCRYPTION_KEY_TYPE_GOOGLE_MANAGED { - # If this is not empty, the encryption key is enabled - key.kmsKeyName != "" - - # If this is empty, the Compute Engine default service account is used - key.kmsKeyServiceAccount == "" } else = shisho.decision.googlecloud.compute.ENCRYPTION_KEY_TYPE_ENCRYPTION_KEY_TYPE_CUSTOMER_MANAGED { # If this is not empty, the encryption key is enabled key.kmsKeyName != "" - # If this is not empty, the customer supplied service account is used - key.kmsKeyServiceAccount != "" - # If this is empty, this is not the customer-supplied encryption key key.sha256 == "" } else = shisho.decision.googlecloud.compute.ENCRYPTION_KEY_TYPE_ENCRYPTION_KEY_TYPE_CUSTOMER_SUPPLIED { diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide.graphql new file mode 100644 index 0000000..b89b7d1 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide.graphql @@ -0,0 +1,18 @@ +{ + googleCloud { + projects { + computeEngine { + instances { + metadata { + id + displayName + } + machineType + confidentialInstanceConfiguration { + enableConfidentialCompute + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide.rego new file mode 100644 index 0000000..7183862 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide.rego @@ -0,0 +1,34 @@ +package policy.googlecloud.compute.instance_confidential_computing + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.computeEngine.instances[_] + + status := confidential_computing(instance) + + d := shisho.decision.googlecloud.compute.instance_confidential_computing({ + "allowed": status != shisho.decision.googlecloud.compute.CONFIDENTIAL_COMPUTING_STATUS_DISABLED, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.compute.instance_confidential_computing_payload({ + "confidential_computing_status": status, + "machine_type": instance.machineType, + }), + }) +} + +confidential_computing(instance) := shisho.decision.googlecloud.compute.CONFIDENTIAL_COMPUTING_STATUS_ENABLED { + is_supported_instance_type(instance.machineType) + instance.confidentialInstanceConfiguration.enableConfidentialCompute == true +} else = shisho.decision.googlecloud.compute.CONFIDENTIAL_COMPUTING_STATUS_UNSUPPORTED { + not is_supported_instance_type(instance.machineType) +} else = shisho.decision.googlecloud.compute.CONFIDENTIAL_COMPUTING_STATUS_DISABLED + +# Confidential Computing is currently only supported on N2D/C2D machines +# https://cloud.google.com/confidential-computing/confidential-vm/docs/os-and-machine-type#machine-type +is_supported_instance_type(machine_type) { + startswith(machine_type, "n2d-") +} else { + startswith(machine_type, "c2d-") +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide_test.rego new file mode 100644 index 0000000..c2a3244 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-confidential-computing/decide_test.rego @@ -0,0 +1,68 @@ +package policy.googlecloud.compute.instance_confidential_computing + +import data.shisho +import future.keywords + +test_whether_confidential_computing_is_enabled_for_compute_engine_instance if { + # check if the confidential computing is enabled for a Google Cloud Compute Engine instances + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 4 with input as {"googleCloud": {"projects": [{"computeEngine": {"instances": [ + { + "metadata": { + "id": "googlecloud-ce-instance|514897777777|us-central1-a|3545084786847777777", + "displayName": "instance-1", + }, + "machineType": "n2d-highcpu-48", + "confidentialInstanceConfiguration": {"enableConfidentialCompute": true}, + }, + { + "metadata": { + "id": "googlecloud-ce-instance|514897777777|asia-northeast2-a|3785461238728888888", + "displayName": "instance-2", + }, + "machineType": "custom-20-61440-ext", + "confidentialInstanceConfiguration": null, + }, + { + "metadata": { + "id": "googlecloud-ce-instance|514897777777|asia-northeast2-a|3785461238729999999", + "displayName": "instance-3", + }, + "machineType": "n2d-highcpu-64", + "confidentialInstanceConfiguration": {"enableConfidentialCompute": true}, + }, + { + "metadata": { + "id": "googlecloud-ce-instance|514897777777|asia-northeast2-a|3785461238720000000", + "displayName": "instance-4", + }, + "machineType": "custom-20-61440-ext", + "confidentialInstanceConfiguration": {"enableConfidentialCompute": false}, + }, + ]}}]}} + + # check if the confidential computing is not enabled for a Google Cloud Compute Engine instances + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"computeEngine": {"instances": [ + { + "metadata": { + "id": "googlecloud-ce-instance|514897777777|us-central1-a|3545084786847777777", + "displayName": "instance-1", + }, + "machineType": "n2d-highcpu-48", + "confidentialInstanceConfiguration": {"enableConfidentialCompute": false}, + }, + { + "metadata": { + "id": "googlecloud-ce-instance|514897777777|asia-northeast2-a|3785461238729999999", + "displayName": "instance-3", + }, + "machineType": "n2d-highcpu-64", + "confidentialInstanceConfiguration": {"enableConfidentialCompute": false}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.graphql index 498f4d5..57ed1e8 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.graphql +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.graphql @@ -3,6 +3,13 @@ query { projects { id computeEngine { + # requires compute.projects.get permission on the project, which is not included in the default setup + projectMetadata { + items { + key + value + } + } instances { metadata { id diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.rego index ce8447d..2891b4c 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.rego +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide.rego @@ -6,8 +6,7 @@ decisions[d] { project := input.googleCloud.projects[_] instance := project.computeEngine.instances[_] - enabled := is_oslogin_enabled(instance.instanceMetadata) - + enabled := is_oslogin_enabled(project.computeEngine.projectMetadata, instance.instanceMetadata) d := shisho.decision.googlecloud.compute.instance_oslogin({ "allowed": enabled, "subject": instance.metadata.id, @@ -15,12 +14,20 @@ decisions[d] { }) } -is_oslogin_enabled(metadata) { - # the number of medata items shoud be greater than 0 - count(metadata.items) > 0 +# The instance-level metadata overrides the project-level metadata, and the default is disabled. +# +# Project-level metadata requires compute.projects.get permission on the project, which is not included in the default setup. +# If you want to use project-level metadata for supressing the metadata, you need to add the permission to the service account. +is_oslogin_enabled(projectMetadata, instanceMetadata) := x { + count(instanceMetadata.items) > 0 + + item := instanceMetadata.items[_] + item.key == "enable-oslogin" + x := any([item.value == "1", lower(item.value) == "true"]) +} else := x { + count(projectMetadata.items) > 0 - # check if the metadata item with key "enable-oslogin" exists and value is "true" - item := metadata.items[_] + item := projectMetadata.items[_] item.key == "enable-oslogin" - lower(item.value) == "true" + x := any([item.value == "1", lower(item.value) == "true"]) } else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide_test.rego index 2ce7e05..2c572e2 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide_test.rego +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-oslogin/decide_test.rego @@ -10,24 +10,27 @@ test_whether_os_login_is_enabled_for_compute_engine_instance if { shisho.decision.is_allowed(d) ]) == 2 with input as {"googleCloud": {"projects": [{ "number": 354711641168, - "computeEngine": {"instances": [ - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, - "name": "pod", - "instanceMetadata": {"items": [{ - "key": "enable-oslogin", - "value": "TRUE", - }]}, - }, - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903333"}, - "name": "yours", - "instanceMetadata": {"items": [{ - "key": "enable-oslogin", - "value": "TRUE", - }]}, - }, - ]}, + "computeEngine": { + "projectMetadata": {"items": []}, + "instances": [ + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, + "name": "pod", + "instanceMetadata": {"items": [{ + "key": "enable-oslogin", + "value": "TRUE", + }]}, + }, + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903333"}, + "name": "yours", + "instanceMetadata": {"items": [{ + "key": "enable-oslogin", + "value": "TRUE", + }]}, + }, + ], + }, }]}} # check if the OS login is enabled for a Google Cloud Compute Engine instance @@ -36,24 +39,27 @@ test_whether_os_login_is_enabled_for_compute_engine_instance if { not shisho.decision.is_allowed(d) ]) == 1 with input as {"googleCloud": {"projects": [{ "number": 354711641168, - "computeEngine": {"instances": [ - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, - "name": "pod", - "instanceMetadata": {"items": [{ - "key": "enable-oslogin", - "value": "FALSE", - }]}, - }, - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, - "name": "bunch", - "instanceMetadata": {"items": [{ - "key": "enable-oslogin", - "value": "TRUE", - }]}, - }, - ]}, + "computeEngine": { + "projectMetadata": {"items": []}, + "instances": [ + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, + "name": "pod", + "instanceMetadata": {"items": [{ + "key": "enable-oslogin", + "value": "FALSE", + }]}, + }, + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, + "name": "bunch", + "instanceMetadata": {"items": [{ + "key": "enable-oslogin", + "value": "TRUE", + }]}, + }, + ], + }, }]}} # check if the OS login is disabled for a Google Cloud Compute Engine instance @@ -63,28 +69,31 @@ test_whether_os_login_is_enabled_for_compute_engine_instance if { not shisho.decision.is_allowed(d) ]) == 2 with input as {"googleCloud": {"projects": [{ "number": 354711641168, - "computeEngine": {"instances": [ - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, - "name": "pod", - "instanceMetadata": {"items": []}, - }, - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, - "name": "pod", - "instanceMetadata": {"items": [{ - "key": "startup-script", - "value": "test-script", - }]}, - }, - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, - "name": "bunch", - "instanceMetadata": {"items": [{ - "key": "cluster-location", - "value": "asia-northeast1-a", - }]}, - }, - ]}, + "computeEngine": { + "projectMetadata": {"items": []}, + "instances": [ + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, + "name": "pod", + "instanceMetadata": {"items": []}, + }, + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, + "name": "pod", + "instanceMetadata": {"items": [{ + "key": "startup-script", + "value": "test-script", + }]}, + }, + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, + "name": "bunch", + "instanceMetadata": {"items": [{ + "key": "cluster-location", + "value": "asia-northeast1-a", + }]}, + }, + ], + }, }]}} } diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.graphql index ea654ce..57ed1e8 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.graphql +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.graphql @@ -3,11 +3,18 @@ query { projects { id computeEngine { + # requires compute.projects.get permission on the project, which is not included in the default setup + projectMetadata { + items { + key + value + } + } instances { metadata { id } - metadata { + instanceMetadata { items { key value diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.rego index dd8b5ae..aa2ba0d 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.rego +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide.rego @@ -6,8 +6,7 @@ decisions[d] { project := input.googleCloud.projects[_] instance := project.computeEngine.instances[_] - enabled := is_serial_port_enabled(instance.instanceMetadata) - + enabled := is_serial_port_enabled(project.computeEngine.projectMetadata, instance.instanceMetadata) d := shisho.decision.googlecloud.compute.instance_serial_port({ "allowed": enabled == false, "subject": instance.metadata.id, @@ -15,10 +14,20 @@ decisions[d] { }) } -is_serial_port_enabled(metadata) { - count(metadata.items) > 0 +# The instance-level metadata overrides the project-level metadata, and the default is disabled. +# +# Project-level metadata requires compute.projects.get permission on the project, which is not included in the default setup. +# If you want to use project-level metadata for supressing the metadata, you need to add the permission to the service account. +is_serial_port_enabled(projectMetadata, instanceMetadata) := x { + count(instanceMetadata.items) > 0 + + item := instanceMetadata.items[_] + item.key == "serial-port-enable" + x := any([item.value == "1", lower(item.value) == "true"]) +} else := x { + count(projectMetadata.items) > 0 - item := metadata.items[_] + item := projectMetadata.items[_] item.key == "serial-port-enable" - any([item.value == "1", lower(item.value) == "true"]) + x := any([item.value == "1", lower(item.value) == "true"]) } else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide_test.rego index c71a28d..f92639c 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide_test.rego +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/instance-serial-port/decide_test.rego @@ -10,24 +10,27 @@ test_whether_serial_port_is_disabled_for_compute_engine_instance if { shisho.decision.is_allowed(d) ]) == 2 with input as {"googleCloud": {"projects": [{ "number": 354711641168, - "computeEngine": {"instances": [ - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, - "name": "pod", - "instanceMetadata": {"items": [{ - "key": "serial-port-enable", - "value": "false", - }]}, - }, - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903333"}, - "name": "yours", - "instanceMetadata": {"items": [{ - "key": "serial-port-enable", - "value": "0", - }]}, - }, - ]}, + "computeEngine": { + "projectMetadata": {"items": []}, + "instances": [ + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, + "name": "pod", + "instanceMetadata": {"items": [{ + "key": "serial-port-enable", + "value": "false", + }]}, + }, + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903333"}, + "name": "yours", + "instanceMetadata": {"items": [{ + "key": "serial-port-enable", + "value": "0", + }]}, + }, + ], + }, }]}} # check if the serial port is enabled for a Google Cloud Compute Engine instance @@ -36,24 +39,27 @@ test_whether_serial_port_is_disabled_for_compute_engine_instance if { not shisho.decision.is_allowed(d) ]) == 1 with input as {"googleCloud": {"projects": [{ "number": 354711641168, - "computeEngine": {"instances": [ - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, - "name": "pod", - "instanceMetadata": {"items": [{ - "key": "serial-port-enable", - "value": "false", - }]}, - }, - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, - "name": "bunch", - "instanceMetadata": {"items": [{ - "key": "serial-port-enable", - "value": "TRUE", - }]}, - }, - ]}, + "computeEngine": { + "projectMetadata": {"items": []}, + "instances": [ + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, + "name": "pod", + "instanceMetadata": {"items": [{ + "key": "serial-port-enable", + "value": "false", + }]}, + }, + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, + "name": "bunch", + "instanceMetadata": {"items": [{ + "key": "serial-port-enable", + "value": "TRUE", + }]}, + }, + ], + }, }]}} # check if the serial port is disable for a Google Cloud Compute Engine instance @@ -63,20 +69,23 @@ test_whether_serial_port_is_disabled_for_compute_engine_instance if { shisho.decision.is_allowed(d) ]) == 2 with input as {"googleCloud": {"projects": [{ "number": 354711641168, - "computeEngine": {"instances": [ - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, - "name": "pod", - "instanceMetadata": {"items": []}, - }, - { - "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, - "name": "bunch", - "instanceMetadata": {"items": [{ - "key": "startup-script", - "value": "test-script", - }]}, - }, - ]}, + "computeEngine": { + "projectMetadata": {"items": []}, + "instances": [ + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|7816137240199088770"}, + "name": "pod", + "instanceMetadata": {"items": []}, + }, + { + "metadata": {"id": "google-cloud-ce-instance|354711641168|asia-northeast2-a|8881795636229903417"}, + "name": "bunch", + "instanceMetadata": {"items": [{ + "key": "startup-script", + "value": "test-script", + }]}, + }, + ], + }, }]}} } diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/manifest.yaml index 86af370..f4eb4f8 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/compute/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/compute/manifest.yaml @@ -8,10 +8,10 @@ triggers: - cron: "0 */1 * * *" jobs: - - id: instance-service-account - name: Review service accounts attached to instances + - id: disk-encryption-key + name: Review disk encryption keys for Compute Engine instances decide: - rego: !include instance-service-account/decide.rego + rego: !include disk-encryption-key/decide.rego with: resource_exceptions: type: resource_exception @@ -19,7 +19,31 @@ jobs: description: A special list of resource exceptions values: [] input: - schema: !include instance-service-account/decide.graphql + schema: !include disk-encryption-key/decide.graphql + - id: instance-confidential-computing + name: Review Confidential VM for Compute Engine instances + decide: + rego: !include instance-confidential-computing/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-confidential-computing/decide.graphql + - id: instance-ip-forwarding + name: Review IP forwarding for Compute Engine instances + decide: + rego: !include instance-ip-forwarding/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-ip-forwarding/decide.graphql - id: instance-oauth2-scope name: Review OAuth2 scopes decide: @@ -32,6 +56,18 @@ jobs: values: [] input: schema: !include instance-oauth2-scope/decide.graphql + - id: instance-oslogin + name: Review OS Login for Compute Engine instances + decide: + rego: !include instance-oslogin/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-oslogin/decide.graphql - id: instance-project-wide-key-management name: Review whether "project-wide SSH keys" is not enabled decide: @@ -44,3 +80,51 @@ jobs: values: [] input: schema: !include instance-project-wide-key-management/decide.graphql + - id: instance-public-ip + name: Review IP assignments for Compute Engine instances + decide: + rego: !include instance-public-ip/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-public-ip/decide.graphql + - id: instance-serial-port + name: Review serial port status for Compute Engine instances + decide: + rego: !include instance-serial-port/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-serial-port/decide.graphql + - id: instance-service-account + name: Review service accounts attached to instances + decide: + rego: !include instance-service-account/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-service-account/decide.graphql + - id: instance-shielded-vm + name: Review shielded VM status for Compute Engine instances + decide: + rego: !include instance-shielded-vm/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-shielded-vm/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide.graphql new file mode 100644 index 0000000..8766c31 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide.graphql @@ -0,0 +1,27 @@ +{ + googleCloud { + projects { + credentials { + apiKeys { + metadata { + id + displayName + } + deletedAt + restriction { + applicationRestriction { + ... on GoogleCloudAPIKeyApplicationBrowserRestriction { + __typename + allowedReferrers + } + ... on GoogleCloudAPIKeyApplicationServerRestriction { + __typename + allowedIpAddresses + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide.rego new file mode 100644 index 0000000..996ff29 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide.rego @@ -0,0 +1,74 @@ +package policy.googlecloud.credential.api_keys_restriction + +import data.shisho + +# Parameters +################# +# The following string slices are used to check if the allowed referrers are secure +# Please manage the below keyword sets to fit your needs! + +forbidden_hosts := ["0.0.0.0", "0.0.0.0/0", "::0"] + +referrer_forbidden_params := ["*"] + +referrer_forbidden_prefixes := ["*."] + +referrer_forbidden_postfixes := [".*"] + +# Detection logic +################# + +decisions[d] { + project := input.googleCloud.projects[_] + key := project.credentials.apiKeys[_] + key.deletedAt == null + + d := shisho.decision.googlecloud.credential.api_keys_restriction({ + "allowed": has_enough_restriction(key), + "subject": key.metadata.id, + "payload": shisho.decision.googlecloud.credential.api_keys_restriction_payload({ + "restriction_type": restriction_type(key), + "permissive_values": permissive_values(key), + }), + }) +} + +has_enough_restriction(key) { + has_restriction_config(key) + count(permissive_values(key)) == 0 +} else := false + +has_restriction_config(key) { + key.restriction != null + key.restriction.applicationRestriction != null +} else := false + +restriction_type(key) := shisho.decision.googlecloud.credential.RESTRICTION_TYPE_IP_ADDRESS_RESTRICTION { + key.restriction.applicationRestriction.__typename == "GoogleCloudAPIKeyApplicationServerRestriction" +} else := shisho.decision.googlecloud.credential.RESTRICTION_TYPE_REFERRER_RESTRICTION { + key.restriction.applicationRestriction.__typename == "GoogleCloudAPIKeyApplicationBrowserRestriction" +} else := shisho.decision.googlecloud.credential.ANDROID_APP_RESTRICTION { + key.restriction.applicationRestriction.__typename == "GoogleCloudAPIKeyApplicationAndroidRestriction" +} else := shisho.decision.googlecloud.credential.IOS_APP_RESTRICTION { + key.restriction.applicationRestriction.__typename == "GoogleCloudAPIKeyApplicationIosRestriction" +} else := shisho.decision.googlecloud.credential.RESTRICTION_TYPE_NO_RESTRICTION + +permissive_values(key) := [address | + address := key.restriction.applicationRestriction.allowedIpAddresses[_] + address == forbidden_hosts[_] +] { + key.restriction.applicationRestriction.__typename == "GoogleCloudAPIKeyApplicationServerRestriction" +} else := [r | + r := key.restriction.applicationRestriction.allowedReferrers[_] + is_forbidden_referrer_restriction(r) +] { + key.restriction.applicationRestriction.__typename == "GoogleCloudAPIKeyApplicationBrowserRestriction" +} else := [] + +is_forbidden_referrer_restriction(r) { + r == referrer_forbidden_params[_] +} else { + startswith(r, referrer_forbidden_prefixes[_]) +} else { + endswith(r, referrer_forbidden_postfixes[_]) +} else := false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide_test.rego new file mode 100644 index 0000000..a788736 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-restriction/decide_test.rego @@ -0,0 +1,124 @@ +package policy.googlecloud.credential.api_keys_restriction + +import data.shisho +import future.keywords + +test_whether_api_keys_are_restricted_by_hosts_or_apps if { + # check if the API keys are restricted by hosts or apps + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"credentials": {"apiKeys": [ + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f7777777", + "displayName": "test-api-key-1", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationBrowserRestriction", + "allowedReferrers": ["example.com", "example.org"], + }}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "test-api-key-2", + }, + "deletedAt": "2022-01-01T00:00:00Z", + "restriction": {"applicationRestriction": null}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "test-api-key-3", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationServerRestriction", + "allowedIpAddresses": ["213.12.12.234"], + }}, + }, + ]}}]}} + + # check if the API keys are not restricted by hosts or apps + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 7 with input as {"googleCloud": {"projects": [{"credentials": {"apiKeys": [ + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f7777777", + "displayName": "test-api-key-1", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationBrowserRestriction", + "allowedReferrers": ["*"], + }}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f7777778", + "displayName": "test-api-key-2", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationBrowserRestriction", + "allowedReferrers": ["*.com"], + }}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f7777779", + "displayName": "test-api-key-3", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationBrowserRestriction", + "allowedReferrers": ["example.*"], + }}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "test-api-key-4", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": null}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "test-api-key-5", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationServerRestriction", + "allowedIpAddresses": ["0.0.0.0"], + }}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f9999990", + "displayName": "test-api-key-6", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationServerRestriction", + "allowedIpAddresses": ["0.0.0.0/0"], + }}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428f9999991", + "displayName": "test-api-key-7", + }, + "deletedAt": null, + "restriction": {"applicationRestriction": { + "__typename": "GoogleCloudAPIKeyApplicationServerRestriction", + "allowedIpAddresses": ["::0"], + }}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide.graphql new file mode 100644 index 0000000..178a5a7 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide.graphql @@ -0,0 +1,16 @@ +{ + googleCloud { + projects { + credentials { + apiKeys { + metadata { + id + displayName + } + deletedAt + createdAt + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide.rego new file mode 100644 index 0000000..8da171d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide.rego @@ -0,0 +1,34 @@ +package policy.googlecloud.credential.api_keys_rotation + +import data.shisho + +# this policy checks if the API keys are created/rotated within the last 90 days +# please adjust the `must_alert_if_not_rotated_for` variable depending on your needs +must_alert_if_not_rotated_for := 90 + +decisions[d] { + project := input.googleCloud.projects[_] + api_key := project.credentials.apiKeys[_] + api_key.deletedAt == null + + lat := timestamp_ns(api_key.createdAt) + + d := shisho.decision.googlecloud.credential.api_keys_rotation({ + "allowed": rotated_within_days(lat, must_alert_if_not_rotated_for), + "subject": api_key.metadata.id, + "payload": shisho.decision.googlecloud.credential.api_keys_rotation_payload({"created_at": api_key.createdAt}), + }) +} + +timestamp_ns(t) := 0 { + t == null +} else := time.parse_rfc3339_ns(t) + +rotated_within_days(ts, d) { + now := time.now_ns() + + diff_ns := now - ts + + # True if the difference is less than `d` days + diff_ns <= (((1000000000 * 60) * 60) * 24) * d +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide_test.rego new file mode 100644 index 0000000..035b0f0 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-rotation/decide_test.rego @@ -0,0 +1,112 @@ +package policy.googlecloud.credential.api_keys_rotation + +import data.shisho +import future.keywords + +now_ns := time.now_ns() + +today_string := date_string(now_ns) + +six_months_ago_string := date_string(time.add_date(now_ns, 0, -6, 0)) + +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])]) +} + +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) + +test_whether_api_keys_are_rotated if { + # check if the API keys are rotated within `must_alert_if_not_rotated_for` + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "credentials": {"apiKeys": [{ + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428faca562a", + "displayName": "test key 1", + }, + "deletedAt": null, + "createdAt": today_string, + }]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "credentials": {"apiKeys": [ + { + "metadata": { + "id": "googlecloud-cre-api-key|514898888888|47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "test key 2", + }, + "deletedAt": null, + "createdAt": today_string, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|51489999999|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "test key 3", + }, + "deletedAt": six_months_ago_string, + "createdAt": six_months_ago_string, + }, + ]}, + }, + ]}} + + # check if the API keys are rotated within `must_alert_if_not_rotated_for` + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "credentials": {"apiKeys": [{ + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428faca562a", + "displayName": "test key 1", + }, + "deletedAt": null, + "createdAt": six_months_ago_string, + }]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "credentials": {"apiKeys": [ + { + "metadata": { + "id": "googlecloud-cre-api-key|514898888888|47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "test key 2", + }, + "deletedAt": null, + "createdAt": six_months_ago_string, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|51489999999|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "test key 3", + }, + "deletedAt": null, + "createdAt": six_months_ago_string, + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide.graphql new file mode 100644 index 0000000..35da621 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide.graphql @@ -0,0 +1,21 @@ +{ + googleCloud { + projects { + credentials { + apiKeys { + metadata { + id + displayName + } + deletedAt + restriction { + apiTargets { + methods + service + } + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide.rego new file mode 100644 index 0000000..37e61ca --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide.rego @@ -0,0 +1,24 @@ +package policy.googlecloud.credential.api_keys_scope + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + + key := project.credentials.apiKeys[_] + key.deletedAt == null + + d := shisho.decision.googlecloud.credential.api_keys_scope({ + "allowed": has_restriction_target(key), + "subject": key.metadata.id, + "payload": shisho.decision.googlecloud.credential.api_keys_scope_payload({"targets": restriction_targets(key)}), + }) +} + +has_restriction_target(key) { + count(restriction_targets(key)) > 0 +} else := false + +restriction_targets(key) := key.restriction.apiTargets { + key.restriction != null +} else := [] diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide_test.rego new file mode 100644 index 0000000..629bb13 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-scope/decide_test.rego @@ -0,0 +1,102 @@ +package policy.googlecloud.credential.api_keys_scope + +import data.shisho +import future.keywords + +test_whether_api_keys_are_restricted_by_apis if { + # check if the API keys are restricted by APIs + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "credentials": {"apiKeys": [{ + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428faca562a", + "displayName": "test key 1", + }, + "deletedAt": null, + "restriction": {"apiTargets": [{ + "methods": [], + "service": "sql-component.googleapis.com", + }]}, + }]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "credentials": {"apiKeys": [ + { + "metadata": { + "id": "googlecloud-cre-api-key|514898888888|47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "test key 2", + }, + "deletedAt": null, + "restriction": {"apiTargets": [{ + "methods": [], + "service": "sql-component.googleapis.com", + }]}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|51489999999|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "test key 3", + }, + "deletedAt": "2022-01-01T00:00:00Z", + "restriction": null, + }, + ]}, + }, + ]}} + + # check if the API keys are not restricted by APIs + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "credentials": {"apiKeys": [{ + "metadata": { + "id": "googlecloud-cre-api-key|514897777777|47bf78cc-9c32-42c6-a541-d428faca562a", + "displayName": "test key 1", + }, + "deletedAt": null, + "restriction": {"apiTargets": []}, + }]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "credentials": {"apiKeys": [ + { + "metadata": { + "id": "googlecloud-cre-api-key|514898888888|47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "test key 2", + }, + "deletedAt": null, + "restriction": {"apiTargets": []}, + }, + { + "metadata": { + "id": "googlecloud-cre-api-key|51489999999|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "test key 3", + }, + "deletedAt": null, + "restriction": null, + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide.graphql new file mode 100644 index 0000000..8f190ed --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide.graphql @@ -0,0 +1,17 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + credentials { + apiKeys { + name + displayName + deletedAt + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide.rego new file mode 100644 index 0000000..b079ad1 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide.rego @@ -0,0 +1,21 @@ +package policy.googlecloud.credential.api_keys_usage + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + api_keys := active_api_keys(project.credentials.apiKeys) + + d := shisho.decision.googlecloud.credential.api_keys_usage({ + "allowed": count(api_keys) == 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.credential.api_keys_usage_payload({"api_key_names": api_keys}), + }) +} + +active_api_keys(api_keys) := x { + x := [api_key.name | + api_key := api_keys[_] + api_key.deletedAt == null + ] +} else := [] diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide_test.rego new file mode 100644 index 0000000..63964ff --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/api-keys-usage/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.credential.api_keys_usage + +import data.shisho +import future.keywords + +test_whether_api_keys_are_not_created_for_projects if { + # check if the API keys are not created for projects + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "credentials": {"apiKeys": []}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "credentials": {"apiKeys": [{ + "name": "projects/514897777777/locations/global/keys/47bf78cc-9c32-42c6-a541-d428f7777777", + "displayName": "test key 1", + "deletedAt": "2022-01-01T00:00:00Z", + }]}, + }, + ]}} + + # check if the API keys are created for projects + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "credentials": {"apiKeys": [{ + "name": "projects/514897777777/locations/global/keys/47bf78cc-9c32-42c6-a541-d428f7777777", + "displayName": "test key 1", + "deletedAt": null, + }]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "credentials": {"apiKeys": [ + { + "name": "projects/514898888888/locations/global/keys/47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "test key 2", + "deletedAt": null, + }, + { + "name": "projects/514898888888/locations/global/keys/47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "test key 3", + "deletedAt": null, + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/credential/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/manifest.yaml new file mode 100644 index 0000000..ca5302e --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/credential/manifest.yaml @@ -0,0 +1,58 @@ +version: 0.1.0 + +id: "prebundle-googlecloud-credential" +name: "Prebundle: Review Google Cloud Credential posture" + +triggers: + schedule: + - cron: "0 */1 * * *" + +jobs: + - id: api-keys-usage + name: Review API keys do not exist in Google Cloud projects + decide: + rego: !include api-keys-usage/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include api-keys-usage/decide.graphql + - id: api-keys-scope + name: Review scopes for Google Cloud API keys are limited + decide: + rego: !include api-keys-scope/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include api-keys-scope/decide.graphql + - id: api-keys-restriction + name: Review API Keys are restricted to usage by only specified hosts and apps + decide: + rego: !include api-keys-restriction/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include api-keys-restriction/decide.graphql + - id: api-keys-rotation + name: Review API keys are rotated within reasonable days + decide: + rego: !include api-keys-rotation/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include api-keys-rotation/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide.graphql new file mode 100644 index 0000000..ee84d6d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide.graphql @@ -0,0 +1,19 @@ +query { + googleCloud { + projects { + dataproc { + clusters { + metadata { + id + displayName + } + configuration { + encryptionConfiguration { + gcePdKmsKeyName + } + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide.rego new file mode 100644 index 0000000..c34be43 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide.rego @@ -0,0 +1,16 @@ +package policy.googlecloud.dataproc.encryption_key + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + cluster := project.dataproc.clusters[_] + + allowed := cluster.configuration.encryptionConfiguration.gcePdKmsKeyName != "" + + d := shisho.decision.googlecloud.dataproc.encryption_key({ + "allowed": allowed, + "subject": cluster.metadata.id, + "payload": shisho.decision.googlecloud.dataproc.encryption_key_payload({"has_customer_managed_key": allowed}), + }) +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide_test.rego new file mode 100644 index 0000000..fb115e8 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/encryption-key/decide_test.rego @@ -0,0 +1,57 @@ +package policy.googlecloud.dataproc.encryption_key + +import data.shisho +import future.keywords + +test_whether_clusters_are_encrypted_by_encryption_keys if { + # check if the clusters are encrypted by encryption keys + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + {"dataproc": {"clusters": [{ + "metadata": { + "id": "googlecloud-dataproc-cluster|514897777777|47bf78cc-9c32-42c6-a541-d428faca562a", + "displayName": "cluster 1", + }, + "configuration": {"encryptionConfiguration": {"gcePdKmsKeyName": "projects/514897777777/locations/global/keyRings/cluster-1/cryptoKeys/key-1"}}, + }]}}, + {"dataproc": {"clusters": [{ + "metadata": { + "id": "googlecloud-dataproc-cluster|514898888888|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "cluster 3", + }, + "configuration": {"encryptionConfiguration": {"gcePdKmsKeyName": "projects/514898888888/locations/global/keyRings/cluster-3/cryptoKeys/key-2"}}, + }]}}, + ]}} + + # check if the clusters are encrypted by encryption keys + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + {"dataproc": {"clusters": [{ + "metadata": { + "id": "googlecloud-dataproc-cluster|514897777777|47bf78cc-9c32-42c6-a541-d428faca562a", + "displayName": "cluster 1", + }, + "configuration": {"encryptionConfiguration": {"gcePdKmsKeyName": ""}}, + }]}}, + {"dataproc": {"clusters": [ + { + "metadata": { + "id": "googlecloud-dataproc-cluster|514898888888|47bf78cc-9c32-42c6-a541-d428f8888888", + "displayName": "cluster 2", + }, + "configuration": {"encryptionConfiguration": {"gcePdKmsKeyName": ""}}, + }, + { + "metadata": { + "id": "googlecloud-dataproc-cluster|514898888888|47bf78cc-9c32-42c6-a541-d428f9999999", + "displayName": "cluster 3", + }, + "configuration": {"encryptionConfiguration": {"gcePdKmsKeyName": ""}}, + }, + ]}}, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/manifest.yaml new file mode 100644 index 0000000..3495e35 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/dataproc/manifest.yaml @@ -0,0 +1,22 @@ +version: 0.1.0 + +id: "prebundle-googlecloud-dataproc" +name: "Prebundle: Review Google Cloud Dataproc posture" + +triggers: + schedule: + - cron: "0 */1 * * *" + +jobs: + - id: encryption-key + name: Review that the Dataproc cluster is encrypted using customer-managed encryption key + decide: + rego: !include encryption-key/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include encryption-key/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/dns/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/dns/manifest.yaml index c199839..253383b 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/dns/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/dns/manifest.yaml @@ -9,7 +9,7 @@ triggers: jobs: - id: dnssec - name: Review the DNSEC configuration + name: Review the DNSSEC configuration decide: rego: !include dnssec/decide.rego with: diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide.graphql new file mode 100644 index 0000000..e6cb4c2 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide.graphql @@ -0,0 +1,24 @@ +{ + googleCloud { + projects { + cloudFunctions { + functions { + metadata { + id + displayName + } + buildConfiguration { + environmentVariables { + key + } + } + serviceConfiguration { + environmentVariables { + key + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide.rego new file mode 100644 index 0000000..1257a33 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide.rego @@ -0,0 +1,37 @@ +package policy.googlecloud.functions.environment_variables + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + function := project.cloudFunctions.functions[_] + + build_env_variables := [variable.key | + variable := function.buildConfiguration.environmentVariables[_] + ] + + service_env_variables := [variable.key | + variable := function.serviceConfiguration.environmentVariables[_] + ] + + d := shisho.decision.googlecloud.functions.environment_variables({ + "allowed": has_no_env_variables(build_env_variables, service_env_variables), + "subject": function.metadata.id, + "payload": shisho.decision.googlecloud.functions.environment_variables_payload({ + "build_environment_variable_keys": build_env_variables, + "service_environment_variable_keys": service_env_variables, + }), + }) +} + +# This policy reviews key names only, not values. One can improve this detection by using more sophisticated list of keywords, or introducing value-based detection. +suspicious_pieces := ["TOKEN", "SECRET", "KEY", "PASSWORD"] + +has_no_env_variables(build_env_variables, service_env_variables) { + not contains_suspicious_key(build_env_variables) + not contains_suspicious_key(service_env_variables) +} else = false + +contains_suspicious_key(keys) { + contains(upper(keys[_]), suspicious_pieces[_]) +} else := false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide_test.rego new file mode 100644 index 0000000..f27dac6 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/environment-variables/decide_test.rego @@ -0,0 +1,60 @@ +package policy.googlecloud.functions.environment_variables + +import data.shisho +import future.keywords + +test_whether_environment_variables_are_not_used_for_cloud_functions if { + # check if environment variables are not used for Cloud Functions + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudFunctions": {"functions": [ + { + "metadata": { + "id": "googlecloud-cf-function|514897777777|projects/test-project-1/locations/asia-northeast1/functions/function-1", + "displayName": "projects/test-project-1/locations/asia-northeast1/functions/function-1", + }, + "buildConfiguration": {"environmentVariables": []}, + "serviceConfiguration": {"environmentVariables": []}, + }, + { + "metadata": { + "id": "googlecloud-cf-function|514897777777|projects/test-project-1/locations/asia-northeast1/functions/function-2", + "displayName": "projects/test-project-1/locations/asia-northeast1/functions/function-2", + }, + "buildConfiguration": {"environmentVariables": []}, + "serviceConfiguration": {"environmentVariables": []}, + }, + ]}}]}} + + # check if environment variables are not used for Cloud Functions + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudFunctions": {"functions": [ + { + "metadata": { + "id": "googlecloud-cf-function|514897777777|projects/test-project-1/locations/asia-northeast1/functions/function-1", + "displayName": "projects/test-project-1/locations/asia-northeast1/functions/function-1", + }, + "buildConfiguration": {"environmentVariables": [{"key": "TEST_VALUE"}]}, + "serviceConfiguration": {"environmentVariables": [{"key": "TEST_RUNTIME_KEY"}]}, + }, + { + "metadata": { + "id": "googlecloud-cf-function|514897777777|projects/test-project-1/locations/asia-northeast1/functions/function-2", + "displayName": "projects/test-project-1/locations/asia-northeast1/functions/function-2", + }, + "buildConfiguration": {"environmentVariables": [{"key": "TEST_VALUE_PASSWORD"}]}, + "serviceConfiguration": {"environmentVariables": []}, + }, + { + "metadata": { + "id": "googlecloud-cf-function|514897777777|projects/test-project-1/locations/asia-northeast1/functions/function-3", + "displayName": "projects/test-project-1/locations/asia-northeast1/functions/function-3", + }, + "buildConfiguration": {"environmentVariables": []}, + "serviceConfiguration": {"environmentVariables": [{"key": "TEST_RUNTIME_TOKEN"}]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/functions/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/manifest.yaml new file mode 100644 index 0000000..5438701 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/functions/manifest.yaml @@ -0,0 +1,22 @@ +version: 0.1.0 + +id: "prebundle-googlecloud-functions" +name: "Prebundle: Review Google Cloud Functions posture" + +triggers: + schedule: + - cron: "0 */1 * * *" + +jobs: + - id: environment-variables + name: Review Cloud Functions environment variables + decide: + rego: !include environment-variables/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include environment-variables/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 c1e2f4d..9f02eff 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/manifest.yaml @@ -8,7 +8,7 @@ triggers: - cron: "0 */1 * * *" jobs: - - id: service-account-impersonation + - id: service-account-project-impersonation-role name: Review the possibility of the service account impersonation decide: rego: !include service-account-project-impersonation-role/decide.rego @@ -32,7 +32,7 @@ jobs: values: [] input: schema: !include service-account-key/decide.graphql - - id: service-account-admin-role + - id: service-account-project-admin-role name: Review service account project admin roles decide: rego: !include service-account-project-admin-role/decide.rego @@ -43,4 +43,50 @@ jobs: description: A special list of resource exceptions values: [] input: - schema: !include service-account-project-admin-role/decide.graphql \ No newline at end of file + schema: !include service-account-project-admin-role/decide.graphql + - id: principal-source + name: Review principal sources + decide: + rego: !include principal-source/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + allowed_principal_domains: + type: string + multiple: true + description: A list of domain names (e.g. example.com) that are allowed to be used as a principal source + values: [] + allowed_principal_domain_regexes: + type: string + multiple: true + description: A list of domain name regexes (e.g. .*\.example\.com) that are allowed to be used as a principal source + values: [] + input: + schema: !include principal-source/decide.graphql + - id: service-account-admin-separation + name: Review that separation of duties is enforced while assigning service account related roles to users + decide: + rego: !include service-account-admin-separation/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include service-account-admin-separation/decide.graphql + - id: service-account-key-rotation + name: Review that the user-managed/external keys for service accounts are rotated every 90 days or fewer + decide: + rego: !include service-account-key-rotation/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include service-account-key-rotation/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide.graphql new file mode 100644 index 0000000..89cb350 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide.graphql @@ -0,0 +1,33 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + iamPolicy { + bindings { + members { + ... on GoogleCloudIAMPrincipalUser { + __typename + id + email + deleted + } + ... on GoogleCloudIAMPrincipalGroup { + __typename + id + email + deleted + } + ... on GoogleCloudIAMPrincipalDomain { + __typename + id + domain + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide.rego new file mode 100644 index 0000000..f64fac3 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide.rego @@ -0,0 +1,59 @@ +package policy.googlecloud.iam.principal_source + +import data.shisho +import future.keywords.every + +# please adjust these lists to your needs +# the list of allowed source domains +allowed_source_domains := data.params.allowed_principal_domains { + data.params != null + data.params.allowed_principal_domains != null +} else := ["testcompany.com", "testcompany2.com"] + +# the list of allowed source domains using regex +allowed_regex_source_domains := data.params.allowed_principal_domain_regexes { + data.params != null + data.params.allowed_principal_domain_regexes != null +} else := ["testcompany.*\\.com$"] + +decisions[d] { + project := input.googleCloud.projects[_] + ds := domains(project.iamPolicy.bindings) + + d := shisho.decision.googlecloud.iam.principal_source({ + "allowed": has_only_allowed_domains(ds), + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.iam.principal_source_payload({"source_domains": ds}), + }) +} + +domains(bindings) := x { + x := {domain | + binding := bindings[_] + member := binding.members[_] + [ + "GoogleCloudIAMPrincipalUser", + "GoogleCloudIAMPrincipalGroup", + ][_] == member.__typename + + not member.deleted + elements := split(member.email, "@") + domain := elements[1] + } | {member.domain | + binding := bindings[_] + member := binding.members[_] + member.__typename == "GoogleCloudIAMPrincipalDomain" + } +} else = {} + +has_only_allowed_domains(ds) { + every d in ds { + is_allowed_domain(d) + } +} else := false + +is_allowed_domain(d) { + allowed_source_domains[_] == d +} else { + regex.match(allowed_regex_source_domains[_], d) +} else := false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide_test.rego new file mode 100644 index 0000000..afea412 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/principal-source/decide_test.rego @@ -0,0 +1,105 @@ +package policy.googlecloud.iam.principal_source + +import data.shisho +import future.keywords + +test_whether_principal_sources_for_projects_are_allowed if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "iamPolicy": {"bindings": []}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "iamPolicy": {"bindings": [{"members": []}]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-3", + }, + "iamPolicy": {"bindings": [{"members": [ + { + "__typename": "GoogleCloudIAMPrincipalUser", + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": true, + }, + { + "__typename": "GoogleCloudIAMPrincipalGroup", + "id": "user:test-group-1@flatt.tech", + "email": "test-group-1@flatt.tech", + "deleted": true, + }, + ]}]}, + }, + ]}} +} + +test_whether_principal_sources_for_projects_are_denied if { + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "iamPolicy": {"bindings": [{"members": [ + { + "__typename": "GoogleCloudIAMPrincipalUser", + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }, + { + "__typename": "GoogleCloudIAMPrincipalGroup", + "id": "user:test-group-1@flatt.tech", + "email": "test-group-1@flatt.tech", + "deleted": false, + }, + ]}]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-3", + }, + "iamPolicy": {"bindings": [{"members": [ + { + "__typename": "GoogleCloudIAMPrincipalUser", + "id": "user:test-user-2@flatt.tech", + "email": "test-user-2@flatt.tech", + "deleted": false, + }, + { + "__typename": "GoogleCloudIAMPrincipalGroup", + "id": "user:test-group-2@flatt.tech", + "email": "test-group-2@flatt.tech", + "deleted": false, + }, + ]}]}, + }, + { + "metadata": { + "id": "googlecloud-project|514890000000", + "displayName": "test-project-4", + }, + "iamPolicy": {"bindings": [{"members": [{ + "__typename": "GoogleCloudIAMPrincipalDomain", + "id": "domain:example.tech", + "domain": "example.tech", + }]}]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide.graphql new file mode 100644 index 0000000..532948c --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + iamPolicy { + bindings { + role + members { + ... on GoogleCloudIAMPrincipalServiceAccount { + id + email + deleted + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide.rego new file mode 100644 index 0000000..ae2b6e5 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide.rego @@ -0,0 +1,47 @@ +package policy.googlecloud.iam.service_account_admin_separation + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + + a := admins(project.iamPolicy.bindings) + ub := user_bindings(project.iamPolicy.bindings) + + d := shisho.decision.googlecloud.iam.service_account_admin_separation({ + "allowed": count(a & {u.principal | u := ub[_]}) == 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.iam.service_account_admin_separation_payload({ + "admin_principals": a, + "users": ub, + }), + }) +} + +admins(bindings) := x { + x := {member.id | + binding := bindings[_] + binding.role == "roles/iam.serviceAccountAdmin" + + member := binding.members[_] + member.deleted == false + } +} else = [] + +user_roles := [ + "roles/iam.serviceAccountTokenCreator", + "roles/iam.serviceAccountUser", +] + +user_bindings(bindings) := x { + x := {{ + "principal": member.id, + "role": binding.role, + } | + binding := bindings[_] + binding.role == user_roles[_] + + member := binding.members[_] + member.deleted == false + } +} else = [] diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide_test.rego new file mode 100644 index 0000000..5840973 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-admin-separation/decide_test.rego @@ -0,0 +1,147 @@ +package policy.googlecloud.iam.service_account_admin_separation + +import data.shisho +import future.keywords + +test_whether_admin_service_accounts_do_not_have_user_role if { + # check if admin service accounts do not have user roles + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 5 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/iam.serviceAccountAdmin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/editor", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/iam.serviceAccountAdmin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-3", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/iam.serviceAccountAdmin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/iam.serviceAccountUser", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": true, + }], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514890000000", + "displayName": "test-project-4", + }, + "iamPolicy": {"bindings": []}, + }, + { + "metadata": { + "id": "googlecloud-project|514891111111", + "displayName": "test-project-5", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/iam.serviceAccountUser", + "members": [], + }]}, + }, + ]}} + + # check if admin service accounts have user roles + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/iam.serviceAccountAdmin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/iam.serviceAccountUser", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-2", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/iam.serviceAccountAdmin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/iam.serviceAccountUser", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide.graphql new file mode 100644 index 0000000..f2aa622 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide.graphql @@ -0,0 +1,21 @@ +{ + googleCloud { + projects { + iam { + serviceAccounts { + metadata { + id + } + name + keys { + name + disabled + origin + type + validAfterAt + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide.rego new file mode 100644 index 0000000..f604e86 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.iam.service_account_key_rotation + +import data.shisho + +# this policy checks if the key are rotated within the last 90 days +# please adjust the `must_alert_if_not_rotated_for` variable depending on your needs +must_alert_if_not_rotated_for := 45 + +decisions[d] { + project := input.googleCloud.projects[_] + account := project.iam.serviceAccounts[_] + + keys := user_managed_keys(account.keys) + allowed := count(keys) == 0 + + d := shisho.decision.googlecloud.iam.service_account_key_rotation({ + "allowed": allowed, + "subject": account.metadata.id, + "payload": shisho.decision.googlecloud.iam.service_account_key_rotation_payload({"keys": keys}), + }) +} + +user_managed_keys(keys) := x { + x := [{"name": key.name, "valid_after_at": key.validAfterAt} | + key := keys[_] + key.type == "USER_MANAGED" + key.disabled == false + lat := timestamp_ns(key.validAfterAt) + rotated_within_recent_days(lat, must_alert_if_not_rotated_for) + ] +} + +timestamp_ns(t) := 0 { + t == null +} else := time.parse_rfc3339_ns(t) + +rotated_within_recent_days(ts, d) { + now := time.now_ns() + + diff_ns := now - ts + + # True if the difference is greater than `d` days + diff_ns > (((1000000000 * 60) * 60) * 24) * d +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide_test.rego new file mode 100644 index 0000000..44e2a02 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/iam/service-account-key-rotation/decide_test.rego @@ -0,0 +1,123 @@ +package policy.googlecloud.iam.service_account_key_rotation + +import data.shisho +import future.keywords + +now_ns := time.now_ns() + +today_string := date_string(now_ns) + +six_months_ago_string := date_string(time.add_date(now_ns, 0, -5, 0)) + +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])]) +} + +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) + +test_whether_keys_of_service_accounts_are_rotated if { + # check if the keys of service accounts are rotated within `must_alert_if_not_rotated_for` + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"iam": {"serviceAccounts": [ + { + "metadata": {"id": "googlecloud-iam-sa|514897777777|112977612696787777777"}, + "name": "projects/test-project-1/serviceAccounts/test-sa-1@test-project-1.iam.gserviceaccount.com", + "keys": [ + { + "name": "projects/test-project-1/serviceAccounts/test-sa-1@test-project-1.iam.gserviceaccount.com/keys/0727440ed4a7f0f11cd8fdedae9c8c8fdc78fc60", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": today_string, + }, + { + "name": "projects/test-project-1/serviceAccounts/test-sa-1@test-project-1.iam.gserviceaccount.com/keys/44b9920dd7a8b1e94ef46774406c210d2e179a69", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": today_string, + }, + ], + }, + { + "metadata": {"id": "googlecloud-iam-sa|514898888888|113958550370488888888"}, + "name": "projects/test-project-1/serviceAccounts/test-sa-2@test-project-1.iam.gserviceaccount.com", + "keys": [ + { + "name": "projects/test-project-1/serviceAccounts/test-sa-2@test-project-1.iam.gserviceaccount.com/keys/148eb84058a3c0d02f42615842fda072d3512f26", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": today_string, + }, + { + "name": "projects/test-project-1/serviceAccounts/test-sa-2@test-project-1.iam.gserviceaccount.com/keys/fce2154a4ec3f69714053cd725e6a3ef4851ec09", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "SYSTEM_MANAGED", + "validAfterAt": six_months_ago_string, + }, + { + "name": "projects/test-project-1/serviceAccounts/test-sa-2@test-project-1.iam.gserviceaccount.com/keys/148eb84058a3c0d02f42615842fda072d3512f26", + "disabled": true, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": six_months_ago_string, + }, + ], + }, + ]}}]}} + + # check if the keys of service accounts are not rotated within `must_alert_if_not_rotated_for` + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"iam": {"serviceAccounts": [ + { + "metadata": {"id": "googlecloud-iam-sa|514897777777|112977612696787777777"}, + "name": "projects/test-project-1/serviceAccounts/test-sa-1@test-project-1.iam.gserviceaccount.com", + "keys": [ + { + "name": "projects/test-project-1/serviceAccounts/test-sa-1@test-project-1.iam.gserviceaccount.com/keys/0727440ed4a7f0f11cd8fdedae9c8c8fdc78fc60", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": six_months_ago_string, + }, + { + "name": "projects/test-project-1/serviceAccounts/test-sa-1@test-project-1.iam.gserviceaccount.com/keys/44b9920dd7a8b1e94ef46774406c210d2e179a69", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": six_months_ago_string, + }, + ], + }, + { + "metadata": {"id": "googlecloud-iam-sa|514898888888|113958550370488888888"}, + "name": "projects/test-project-1/serviceAccounts/test-sa-2@test-project-1.iam.gserviceaccount.com", + "keys": [ + { + "name": "projects/test-project-1/serviceAccounts/test-sa-2@test-project-1.iam.gserviceaccount.com/keys/148eb84058a3c0d02f42615842fda072d3512f26", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": six_months_ago_string, + }, + { + "name": "projects/test-project-1/serviceAccounts/test-sa-2@test-project-1.iam.gserviceaccount.com/keys/fce2154a4ec3f69714053cd725e6a3ef4851ec09", + "disabled": false, + "origin": "USER_PROVIDED", + "type": "USER_MANAGED", + "validAfterAt": today_string, + }, + ], + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide.graphql new file mode 100644 index 0000000..6a63d06 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide.graphql @@ -0,0 +1,27 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + iamPolicy { + bindings { + role + members { + ... on GoogleCloudIAMPrincipalUser { + id + email + deleted + } + ... on GoogleCloudIAMPrincipalServiceAccount { + id + email + deleted + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide.rego new file mode 100644 index 0000000..ce8fa99 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide.rego @@ -0,0 +1,49 @@ +package policy.googlecloud.kms.admin_separation + +import data.shisho + +# the roles should not be owned by principals having a KMS admin role +user_roles := [ + "roles/cloudkms.cryptoKeyDecrypter", + "roles/cloudkms.cryptoKeyEncrypter", + "roles/cloudkms.cryptoKeyEncrypterDecrypter", +] + +decisions[d] { + project := input.googleCloud.projects[_] + + a := admins(project.iamPolicy.bindings) + ub := user_bindings(project.iamPolicy.bindings) + + d := shisho.decision.googlecloud.kms.admin_separation({ + "allowed": count(a & {u.principal | u := ub[_]}) == 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.kms.admin_separation_payload({ + "admin_principals": a, + "users": ub, + }), + }) +} + +admins(bindings) := x { + x := {member.id | + binding := bindings[_] + binding.role == "roles/cloudkms.admin" + + member := binding.members[_] + member.deleted == false + } +} else = [] + +user_bindings(bindings) := x { + x := {{ + "principal": member.id, + "role": binding.role, + } | + binding := bindings[_] + binding.role == user_roles[_] + + member := binding.members[_] + member.deleted == false + } +} else = [] diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide_test.rego new file mode 100644 index 0000000..60d3a63 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/admin-separation/decide_test.rego @@ -0,0 +1,247 @@ +package policy.googlecloud.kms.admin_separation + +import data.shisho +import future.keywords + +test_whether_kms_admins_have_only_admin_role if { + # check if KMS admins have only the KMS admin role + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 5 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/cloudkms.admin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/editor", + "members": [{ + "id": "user:test-user-3@flatt.tech", + "email": "test-user-3@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/owner", + "members": [ + { + "id": "user:test-user-2@flatt.tech", + "email": "test-user-2@flatt.tech", + "deleted": false, + }, + { + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }, + ], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "iamPolicy": {"bindings": []}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-3", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/cloudkms.admin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/cloudkms.cryptoKeyDecrypter", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": true, + }], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514890000000", + "displayName": "test-project-4", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/owner", + "members": [ + { + "id": "user:test-user-2@flatt.tech", + "email": "test-user-2@flatt.tech", + "deleted": false, + }, + { + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }, + ], + }]}, + }, + { + "metadata": { + "id": "googlecloud-project|514891111111", + "displayName": "test-project-5", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/owner", + "members": [], + }]}, + }, + ]}} + + # check if KMS admins do not have any other KMS roles + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/cloudkms.admin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/cloudkms.cryptoKeyDecrypter", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/cloudkms.cryptoKeyEncrypter", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/editor", + "members": [{ + "id": "user:test-user-3@flatt.tech", + "email": "test-user-3@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/owner", + "members": [ + { + "id": "user:test-user-2@flatt.tech", + "email": "test-user-2@flatt.tech", + "deleted": false, + }, + { + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }, + ], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "iamPolicy": {"bindings": [ + { + "role": "roles/cloudkms.admin", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/cloudkms.cryptoKeyDecrypter", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/cloudkms.cryptoKeyEncrypter", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "members": [{ + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/editor", + "members": [{ + "id": "user:test-user-3@flatt.tech", + "email": "test-user-3@flatt.tech", + "deleted": false, + }], + }, + { + "role": "roles/owner", + "members": [ + { + "id": "user:test-user-2@flatt.tech", + "email": "test-user-2@flatt.tech", + "deleted": false, + }, + { + "id": "user:test-user-1@flatt.tech", + "email": "test-user-1@flatt.tech", + "deleted": false, + }, + ], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide.graphql new file mode 100644 index 0000000..49a7070 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide.graphql @@ -0,0 +1,29 @@ +{ + googleCloud { + projects { + kms { + keyRings { + keys { + metadata { + id + displayName + } + iamPolicy { + bindings { + role + members { + ... on GoogleCloudIAMPrincipalAllAuthenticatedUsers { + id + } + ... on GoogleCloudIAMPrincipalAllUsers { + id + } + } + } + } + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide.rego new file mode 100644 index 0000000..1a42f4a --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide.rego @@ -0,0 +1,30 @@ +package policy.googlecloud.kms.key_accessibility + +import data.shisho + +forbidden_principals := [ + "allAuthenticatedUsers", + "allUsers", +] + +decisions[d] { + project := input.googleCloud.projects[_] + key_ring := project.kms.keyRings[_] + key := key_ring.keys[_] + + fb := forbidden_bindings(key.iamPolicy.bindings) + d := shisho.decision.googlecloud.kms.key_accessibility({ + "allowed": count(fb) == 0, + "subject": key.metadata.id, + "payload": shisho.decision.googlecloud.kms.key_accessibility_payload({"forbidden_bindings": fb}), + }) +} + +forbidden_bindings(bindings) = x { + x := {{"principal": member.id, "role": binding.role} | + binding := bindings[_] + + member := binding.members[_] + member.id == forbidden_principals[_] + } +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide_test.rego new file mode 100644 index 0000000..2af45bb --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-accessibility/decide_test.rego @@ -0,0 +1,80 @@ +package policy.googlecloud.kms.key_accessibility + +import data.shisho +import future.keywords + +test_whether_publicly_accessible_of_kms_keys_is_allowed if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"kms": {"keyRings": [ + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|514897777777|projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + "displayName": "projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "members": [], + }]}, + }]}, + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|514897777777|projects/test-project-1/locations/global/keyRings/test-keyring/cryptoKeys/test-key", + "displayName": "projects/test-project-1/locations/global/keyRings/test-keyring/cryptoKeys/test-key", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "members": [], + }]}, + }]}, + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|514897777777|projects/test-project-1/locations/us-west1/keyRings/test-keyring-2/cryptoKeys/test-key-2", + "displayName": "projects/test-project-1/locations/us-west1/keyRings/test-keyring-2/cryptoKeys/test-key-2", + }, + "iamPolicy": {"bindings": []}, + }]}, + ]}}]}} +} + +test_whether_publicly_accessible_of_kms_keys_is_denied if { + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"kms": {"keyRings": [ + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|514897777777|projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + "displayName": "projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "members": [ + {"id": "allUsers"}, + {"id": "allAuthenticatedUsers"}, + ], + }]}, + }]}, + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|514897777777|projects/test-project-1/locations/global/keyRings/test-keyring/cryptoKeys/test-key", + "displayName": "projects/test-project-1/locations/global/keyRings/test-keyring/cryptoKeys/test-key", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "members": [{"id": "allAuthenticatedUsers"}], + }]}, + }]}, + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|514897777777|projects/test-project-1/locations/us-west1/keyRings/test-keyring-2/cryptoKeys/test-key-2", + "displayName": "projects/test-project-1/locations/us-west1/keyRings/test-keyring-2/cryptoKeys/test-key-2", + }, + "iamPolicy": {"bindings": [{ + "role": "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "members": [{"id": "allUsers"}], + }]}, + }]}, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide.graphql new file mode 100644 index 0000000..c1fe7b2 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide.graphql @@ -0,0 +1,18 @@ +{ + googleCloud { + projects { + kms { + keyRings { + keys { + metadata { + id + displayName + } + rotationPeriod + nextRotatedAt + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide.rego new file mode 100644 index 0000000..c93ae7d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.kms.key_rotation + +import data.shisho + +# this policy checks if the KMS keys are rotated within the last 90 days +# please adjust the `must_alert_if_not_rotated_for` variable depending on your needs +threshould_days := 90 + +threshould_seconds := ((60 * 60) * 24) * threshould_days + +decisions[d] { + project := input.googleCloud.projects[_] + key_ring := project.kms.keyRings[_] + key := key_ring.keys[_] + + d := shisho.decision.googlecloud.kms.key_rotation({ + "allowed": is_rotated(key), + "subject": key.metadata.id, + "payload": shisho.decision.googlecloud.kms.key_rotation_payload({ + "rotation_period_seconds": key.rotationPeriod, + "rotation_period_expectation_seconds": threshould_seconds, + "last_rotated_at": key.nextRotatedAt, + }), + }) +} + +is_rotated(key) { + key.rotationPeriod <= threshould_seconds + lat := timestamp_ns(key.nextRotatedAt) + last_rotation_within(lat, threshould_days) +} else = false + +timestamp_ns(t) := 0 { + t == null +} else := time.parse_rfc3339_ns(t) + +last_rotation_within(ts, d) { + now := time.now_ns() + + diff_ns := now - ts + + # True if the difference is less than `d` days + diff_ns <= (((1000000000 * 60) * 60) * 24) * d +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide_test.rego new file mode 100644 index 0000000..9414c0d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/key-rotation/decide_test.rego @@ -0,0 +1,68 @@ +package policy.googlecloud.kms.key_rotation + +import data.shisho +import future.keywords + +now_ns := time.now_ns() + +today_string := date_string(now_ns) + +six_months_ago_string := date_string(time.add_date(now_ns, 0, -6, 0)) + +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])]) +} + +format_digit(digit) = formatted_digit if { + digit < 10 + formatted_digit := sprintf("0%d", [digit]) +} else = sprintf("%d", [digit]) + +test_whether_kms_keys_are_rotated if { + # check if the KMS keys are rotated within `must_alert_if_not_rotated_for` + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"kms": {"keyRings": [ + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|5148932577777|projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + "displayName": "projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + }, + "rotationPeriod": 7776000, + "nextRotatedAt": today_string, + }]}, + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|5148932577777|projects/test-project-1/locations/us-west1/keyRings/test-keyring-2/cryptoKeys/test-key-2", + "displayName": "projects/test-project-1/locations/us-west1/keyRings/test-keyring-2/cryptoKeys/test-key-2", + }, + "rotationPeriod": 1728000, + "nextRotatedAt": today_string, + }]}, + ]}}]}} + + # check if the KMS keys are not rotated within `must_alert_if_not_rotated_for` + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"kms": {"keyRings": [ + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|5148932577777|projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + "displayName": "projects/test-project-1/locations/asia-northeast1/keyRings/test-keyring-3/cryptoKeys/test-key-3", + }, + "rotationPeriod": 7776000, + "nextRotatedAt": six_months_ago_string, + }]}, + {"keys": [{ + "metadata": { + "id": "googlecloud-kms-key|5148932577777|projects/test-project-1/locations/global/keyRings/test-keyring/cryptoKeys/test-key", + "displayName": "projects/test-project-1/locations/global/keyRings/test-keyring/cryptoKeys/test-key", + }, + "rotationPeriod": 31104000, + "nextRotatedAt": today_string, + }]}, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/kms/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/manifest.yaml new file mode 100644 index 0000000..822a590 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/kms/manifest.yaml @@ -0,0 +1,46 @@ +version: 0.1.0 + +id: "prebundle-googlecloud-kms" +name: "Prebundle: Review Google Cloud KMS posture" + +triggers: + schedule: + - cron: "0 */1 * * *" + +jobs: + - id: key-accessibility + name: Review that Cloud KMS cryptokeys are not anonymously or publicly accessible + decide: + rego: !include key-accessibility/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include key-accessibility/decide.graphql + - id: key-rotation + name: Review that KMS encryption keys are rotated within a period of 90 days + decide: + rego: !include key-rotation/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include key-rotation/decide.graphql + - id: admin-separation + name: Review that separation of duties is enforced while assigning KMS related roles to users + decide: + rego: !include admin-separation/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include admin-separation/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide.graphql new file mode 100644 index 0000000..07336f2 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide.graphql @@ -0,0 +1,27 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + sinks { + metadata { + id + } + destination + } + } + cloudStorage { + buckets { + name + retentionPolicy { + isLocked + retentionPeriod + } + } + } + } + } +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide.rego new file mode 100644 index 0000000..ba7e89c --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide.rego @@ -0,0 +1,42 @@ +package policy.googlecloud.logging.bucket_retention_policy + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + + # For a sink with a Cloud Storage bucket as a destination ... + sink := project.cloudLogging.sinks[_] + startswith(sink.destination, "storage.googleapis.com/") + + # ... confirm that the bucket has a locked retention policy. + name := storage_bucket_name(sink.destination) + locked := storage_bucket_locked(name, project) + + d := shisho.decision.googlecloud.logging.bucket_retention_policy({ + "allowed": locked, + "subject": sink.metadata.id, + "payload": shisho.decision.googlecloud.logging.bucket_retention_policy_payload({ + "storage_bucket_name": name, + "locked": locked, + "retention_period": storage_bucket_retention_period(name, project), + }), + }) +} + +storage_bucket_name(destination) := bucket_name { + bucket_name := trim_prefix(destination, "storage.googleapis.com/") +} + +storage_bucket_locked(bucket_name, project) { + bucket := project.cloudStorage.buckets[_] + bucket.name == bucket_name + bucket.retentionPolicy.isLocked == true +} else = false + +storage_bucket_retention_period(bucket_name, project) := p { + bucket := project.cloudStorage.buckets[_] + bucket.name == bucket_name + + p := bucket.retentionPolicy.retentionPeriod +} else = 0 diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide_test.rego new file mode 100644 index 0000000..02b3e62 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/bucket-retention-policy/decide_test.rego @@ -0,0 +1,102 @@ +package policy.googlecloud.logging.bucket_retention_policy + +import data.shisho +import future.keywords + +test_whether_retention_policies_with_bucket_lock_are_configured_for_sinks if { + # check if the retention policies with bucket lock of Cloud Storage buckets are configured for sinks + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{ + "cloudLogging": {"sinks": [ + { + "metadata": { + "id": "googlecloud-cl-sink|51489777777|test-sink-1", + "displayName": "test-sink-1", + }, + "destination": "storage.googleapis.com/test-bucket-shisho-security-12", + }, + { + "metadata": { + "id": "googlecloud-cl-sink|51489777777|test-sink-2", + "displayName": "test-sink-2", + }, + "destination": "storage.googleapis.com/test-bucket-shisho-security-13", + }, + { + "metadata": { + "id": "googlecloud-cl-sink|51489777777|_Default", + "displayName": "_Default", + }, + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/_Default", + }, + ]}, + "cloudStorage": {"buckets": [ + { + "name": "test-bucket-for-dataproc-1", + "retentionPolicy": null, + }, + { + "name": "test-bucket-shisho-security", + "retentionPolicy": null, + }, + { + "name": "test-bucket-shisho-security-12", + "retentionPolicy": {"isLocked": true, "retentionPeriod": 123}, + }, + { + "name": "test-bucket-shisho-security-13", + "retentionPolicy": {"isLocked": true, "retentionPeriod": 123}, + }, + ]}, + }]}} + + # check if the retention policies with bucket lock of Cloud Storage buckets are not configured for sinks + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{ + "cloudLogging": {"sinks": [ + { + "metadata": { + "id": "googlecloud-cl-sink|51489777777|test-sink-1", + "displayName": "test-sink-1", + }, + "destination": "storage.googleapis.com/test-bucket-shisho-security-12", + }, + { + "metadata": { + "id": "googlecloud-cl-sink|51489777777|test-sink-2", + "displayName": "test-sink-2", + }, + "destination": "storage.googleapis.com/test-bucket-shisho-security-13", + }, + { + "metadata": { + "id": "googlecloud-cl-sink|51489777777|_Default", + "displayName": "_Default", + }, + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/_Default", + }, + ]}, + "cloudStorage": {"buckets": [ + { + "name": "test-bucket-for-dataproc-1", + "retentionPolicy": null, + }, + { + "name": "test-bucket-shisho-security", + "retentionPolicy": null, + }, + { + "name": "test-bucket-shisho-security-12", + "retentionPolicy": {"isLocked": false, "retentionPeriod": 123}, + }, + { + "name": "test-bucket-shisho-security-13", + "retentionPolicy": null, + }, + ]}, + }]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide.graphql new file mode 100644 index 0000000..f774996 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide.graphql @@ -0,0 +1,21 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + sinks { + name + + filter + exclusions { + filter + } + destination + } + } + } + } +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide.rego new file mode 100644 index 0000000..1d54bf6 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide.rego @@ -0,0 +1,29 @@ +package policy.googlecloud.logging.full_export + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + empty_filter_sinks := sinks_for_all_entries(project.cloudLogging.sinks) + + d := shisho.decision.googlecloud.logging.full_export({ + "allowed": count(empty_filter_sinks) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logging.full_export_payload({"empty_filter_sinks": empty_filter_sinks}), + }) +} + +sinks_for_all_entries(sinks) := x { + x := [sink.name | + sink := sinks[_] + + # the sink must include all + sink.filter == "" + + # the sink must not exclude any + count(sink.exclusions) == 0 + + # the sink must have a destination + sink.destination != "" + ] +} else = [] diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide_test.rego new file mode 100644 index 0000000..34674e7 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/full-export/decide_test.rego @@ -0,0 +1,100 @@ +package policy.googlecloud.logging.full_export + +import data.shisho +import future.keywords + +test_whether_logging_sinks_are_configured_for_all_log_entries if { + # check if the logging sinks are configured for all log entries + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"sinks": [ + { + "name": "test-sink-1", + "filter": "", + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/test-sink-1", + "exclusions": [], + }, + { + "name": "test-sink-2", + "filter": "LOG_ID(\"cloudaudit.googleapis.com/activity\")", + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/test-sink-1", + "exclusions": [], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"sinks": [ + { + "name": "test-empty-filter-sink-1", + "filter": "", + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/test-sink-1", + "exclusions": [], + }, + { + "name": "sink-1", + "filter": "LOG_ID(\"cloudaudit.googleapis.com/activity\")", + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/test-sink-1", + "exclusions": [], + }, + ]}, + }, + ]}} + + # check if the logging sinks are not configured for all log entries + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"sinks": [ + { + "name": "sink-1", + "filter": "LOG_ID(\"cloudaudit.googleapis.com/activity\")", + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/test-sink-1", + "exclusions": [], + }, + { + "name": "sink-1", + "filter": "LOG_ID(\"cloudaudit.googleapis.com/activity\")", + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/test-sink-1", + "exclusions": [], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"sinks": [ + { + "name": "sink-1", + "filter": "", + "destination": "", + "exclusions": [], + }, + { + "name": "sink-1", + "filter": "LOG_ID(\"cloudaudit.googleapis.com/activity\")", + "destination": "logging.googleapis.com/projects/test-project-1/locations/global/buckets/test-sink-1", + "exclusions": [], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/manifest.yaml index 3f309b4..2981982 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/logging/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logging/manifest.yaml @@ -20,3 +20,27 @@ jobs: values: [] input: schema: !include api-audit/decide.graphql + - id: full-export + name: Review that at least one sink is configured for all log entries + decide: + rego: !include full-export/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include full-export/decide.graphql + - id: bucket-retention-policy + name: Review that Cloud Storage buckets for storing logs are configured using bucket lock + decide: + rego: !include bucket-retention-policy/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include bucket-retention-policy/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide.rego new file mode 100644 index 0000000..3513340 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.audit_config_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.audit_config_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.audit_config_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*"] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide_test.rego new file mode 100644 index 0000000..457dade --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/audit-configuration/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.audit_config_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_audit_config_changes if { + # check if the log metrics are configured for audit configuration changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for audit configuration changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "protoPayload.methodName=\"SetIamPolicy\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide.rego new file mode 100644 index 0000000..5f4915e --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.custom_role_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.custom_role_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.custom_role_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["resource.type=\"iam_role\" AND (protoPayload.methodName=\"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\" OR protoPayload.methodName=\"google.iam.admin.v1.UpdateRole\")"] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide_test.rego new file mode 100644 index 0000000..98f7a9f --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/custom-role/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.custom_role_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_custom_role_changes if { + # check if the log metrics are configured for custom role changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"iam_role\" AND (protoPayload.methodName=\"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\" OR protoPayload.methodName=\"google.iam.admin.v1.UpdateRole\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=\"iam_role\" AND (protoPayload.methodName=\"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\" OR protoPayload.methodName=\"google.iam.admin.v1.UpdateRole\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for custom role changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"iam_role\" AND (protoPayload.methodName=\"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\" OR protoPayload.methodName=\"google.iam.admin.v1.UpdateRole\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=\"iam_role\"\nAND protoPayload.methodName = \"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"iam_role\" AND (protoPayload.methodName=\"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\" OR protoPayload.methodName=\"google.iam.admin.v1.UpdateRole\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide.rego new file mode 100644 index 0000000..773513c --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.firewall_rule_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.firewall_rule_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.firewall_rule_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["resource.type=\"gce_firewall_rule\" AND (protoPayload.methodName:\"compute.firewalls.insert\" OR protoPayload.methodName:\"compute.firewalls.patch\" OR protoPayload.methodName:\"compute.firewalls.delete\")"] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide_test.rego new file mode 100644 index 0000000..66bffbe --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/firewall-rule/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.firewall_rule_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_firewall_rule_changes if { + # check if the log metrics are configured for firewall rule changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_firewall_rule\" AND (protoPayload.methodName:\"compute.firewalls.insert\" OR protoPayload.methodName:\"compute.firewalls.patch\" OR protoPayload.methodName:\"compute.firewalls.delete\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=\"gce_firewall_rule\" AND (protoPayload.methodName:\"compute.firewalls.insert\" OR protoPayload.methodName:\"compute.firewalls.patch\" OR protoPayload.methodName:\"compute.firewalls.delete\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for firewall rule changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_firewall_rule\" AND (protoPayload.methodName:\"compute.firewalls.insert\" OR protoPayload.methodName:\"compute.firewalls.patch\" OR protoPayload.methodName:\"compute.firewalls.delete\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=\"gce_firewall_rule\"\nAND protoPayload.methodName:\"compute.firewalls.patch\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_firewall_rule\" AND (protoPayload.methodName:\"compute.firewalls.insert\" OR protoPayload.methodName:\"compute.firewalls.patch\" OR protoPayload.methodName:\"compute.firewalls.delete\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/manifest.yaml new file mode 100644 index 0000000..09b4da5 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/manifest.yaml @@ -0,0 +1,106 @@ +version: 0.1.0 + +id: "prebundle-googlecloud-logmetric" +name: "Prebundle: Review Google Cloud log metrics posture" + +triggers: + schedule: + - cron: "0 */1 * * *" + +jobs: + - id: storage-iam-permission + name: Review that the log metric filter and alerts exist for Cloud Storage IAM permission changes + decide: + rego: !include storage-iam-permission/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include storage-iam-permission/decide.graphql + - id: custom-role + name: Review that the log metric filter and alerts exist for custom role changes + decide: + rego: !include custom-role/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include custom-role/decide.graphql + - id: sql-instance-configuration + name: Review that the log metric filter and alerts exist for SQL instance configuration changes + decide: + rego: !include sql-instance-configuration/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include sql-instance-configuration/decide.graphql + - id: vpc-network + name: Review that the log metric filter and alerts exist for VPC network changes + decide: + rego: !include vpc-network/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include vpc-network/decide.graphql + - id: audit-configuration + name: Review that the log metric filter and alerts exist for audit configuration changes + decide: + rego: !include audit-configuration/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include audit-configuration/decide.graphql + - id: project-ownership + name: Review that the log metric filter and alerts exist for project ownership assignments/changes + decide: + rego: !include project-ownership/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include project-ownership/decide.graphql + - id: firewall-rule + name: Review that the log metric filter and alerts exist for VPC network firewall rule changes + decide: + rego: !include firewall-rule/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include firewall-rule/decide.graphql + - id: network-route + name: Review that the log metric filter and alerts exist for VPC network route changes + decide: + rego: !include network-route/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include network-route/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide.rego new file mode 100644 index 0000000..bf8da9a --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.network_route_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.network_route_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.network_route_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["resource.type=\"gce_route\" AND (protoPayload.methodName:\"compute.routes.delete\" OR protoPayload.methodName:\"compute.routes.insert\")"] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide_test.rego new file mode 100644 index 0000000..d8abd2d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/network-route/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.network_route_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_network_route_changes if { + # check if the log metrics are configured for VPC network route changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_route\"\nAND (protoPayload.methodName:\"compute.routes.delete\" OR protoPayload.methodName:\"compute.routes.insert\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=\"gce_route\"\nAND (protoPayload.methodName:\"compute.routes.delete\" OR protoPayload.methodName:\"compute.routes.insert\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for VPC network route changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_route\"\nAND (protoPayload.methodName:\"compute.routes.delete\" OR protoPayload.methodName:\"compute.routes.insert\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=\"gce_route\"\nAND (protoPayload.methodName:\"compute.routes.delete\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_route\"\nAND (protoPayload.methodName:\"compute.routes.delete\" OR protoPayload.methodName:\"compute.routes.insert\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide.rego new file mode 100644 index 0000000..4215c39 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.project_ownership_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.project_ownership_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.project_ownership_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")"] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide_test.rego new file mode 100644 index 0000000..6067e2a --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/project-ownership/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.project_ownership_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_project_ownership_changes if { + # check if the log metrics are configured for project ownership changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for project ownership changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide.rego new file mode 100644 index 0000000..7f94a29 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.sql_config_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.sql_config_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.sql_config_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["protoPayload.methodName=\"cloudsql.instances.update\" OR protoPayload.methodName=\"cloudsql.instances.create\" OR protoPayload.methodName=\"cloudsql.instances.delete\""] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide_test.rego new file mode 100644 index 0000000..a653aa2 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/sql-instance-configuration/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.sql_config_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_sql_config_changes if { + # check if the log metrics are configured for Cloud SQL instance configuration changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "protoPayload.methodName=\"cloudsql.instances.update\" OR protoPayload.methodName=\"cloudsql.instances.create\" OR protoPayload.methodName=\"cloudsql.instances.delete\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "protoPayload.methodName=\"cloudsql.instances.update\" OR protoPayload.methodName=\"cloudsql.instances.create\" OR protoPayload.methodName=\"cloudsql.instances.delete\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for Cloud SQL instance configuration changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "protoPayload.methodName=\"cloudsql.instances.update\" OR protoPayload.methodName=\"cloudsql.instances.create\" OR protoPayload.methodName=\"cloudsql.instances.delete\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "protoPayload.methodName=\"cloudsql.instances.delete\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "protoPayload.methodName=\"cloudsql.instances.update\" OR protoPayload.methodName=\"cloudsql.instances.create\" OR protoPayload.methodName=\"cloudsql.instances.delete\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide.rego new file mode 100644 index 0000000..b6d3ba4 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.storage_iam_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.storage_iam_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.storage_iam_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["resource.type=gcs_bucket AND protoPayload.methodName=\"storage.setIamPermissions\""] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide_test.rego new file mode 100644 index 0000000..a434e76 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/storage-iam-permission/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.storage_iam_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_storage_iam_changes if { + # check if the log metrics are configured for Cloud Storage IAM permission changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=gcs_bucket\nAND protoPayload.methodName=\"storage.setIamPermissions\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=gcs_bucket\nAND protoPayload.methodName=\"storage.setIamPermissions\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for Cloud Storage IAM permission changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=gcs_bucket\nAND protoPayload.methodName=\"storage.setIamPermissions\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "protoPayload.methodName=\"SetIamPolicy\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=gcs_bucket\nAND protoPayload.methodName=\"storage.setIamPermissions\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide.graphql new file mode 100644 index 0000000..e832495 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide.graphql @@ -0,0 +1,30 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + cloudLogging { + metrics { + metadata { + id + displayName + } + filter + } + } + cloudMonitoring { + policies { + displayName + enabled + conditions { + threshold { + filter + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide.rego new file mode 100644 index 0000000..73149bc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide.rego @@ -0,0 +1,44 @@ +package policy.googlecloud.logmetric.vpc_network_changes + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + paths := cis_notification_paths(project) + + d := shisho.decision.googlecloud.logmetric.vpc_network_changes({ + "allowed": count(paths) > 0, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.logmetric.vpc_network_changes_payload({"cis_notification_implementations": paths}), + }) +} + +# The Cloud Logging Log Metrics pattern to match the events to notify. +# This includes one defined in CIS Google Cloud Platform Foundations Benchmark v1.3.0 defines, or one defined in Security Command Center. +# If you achieve the similar goal with a different pattern, you can replace this pattern with yours or add your pattern to the array to let it pass this policy. +patterns := ["resource.type=\"gce_network\" AND (protoPayload.methodName:\"compute.networks.insert\" OR protoPayload.methodName:\"compute.networks.patch\" OR protoPayload.methodName:\"compute.networks.delete\" OR protoPayload.methodName:\"compute.networks.removePeering\" OR protoPayload.methodName:\"compute.networks.addPeering\")"] + +# Find a path that implements the notification from Cloud Logging to Cloud Monitoring as described in CIS Google Cloud Platform Foundations Benchmark v1.3.0 +cis_notification_paths(project) := x { + x := [{ + "metric_name": metric.metadata.displayName, + "alert_policy_name": policy.displayName, + } | + # Find a metric that matches the pattern .... + metric := project.cloudLogging.metrics[_] + filter := replace(metric.filter, "\n", " ") + filter == patterns[_] + + # Find a active policy that has a condition for the metric + policy := project.cloudMonitoring.policies[_] + policy.enabled == true + has_condition_for(policy, metric.metadata.displayName) + ] +} else = [] + +has_condition_for(policy, metric_name) { + query := sprintf("metric.type = \"logging.googleapis.com/user/%s\"", [metric_name]) + + condition := policy.conditions[_] + contains(condition.threshold.filter, query) +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide_test.rego new file mode 100644 index 0000000..ff72057 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/logmetric/vpc-network/decide_test.rego @@ -0,0 +1,145 @@ +package policy.googlecloud.logmetric.vpc_network_changes + +import data.shisho +import future.keywords + +test_whether_log_metrics_are_configured_for_vpc_network_changes if { + # check if the log metrics are configured for VPC network changes + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_network\" AND (protoPayload.methodName:\"compute.networks.insert\" OR protoPayload.methodName:\"compute.networks.patch\" OR protoPayload.methodName:\"compute.networks.delete\" OR protoPayload.methodName:\"compute.networks.removePeering\" OR protoPayload.methodName:\"compute.networks.addPeering\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=\"gce_network\" AND (protoPayload.methodName:\"compute.networks.insert\" OR protoPayload.methodName:\"compute.networks.patch\" OR protoPayload.methodName:\"compute.networks.delete\" OR protoPayload.methodName:\"compute.networks.removePeering\" OR protoPayload.methodName:\"compute.networks.addPeering\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + ]}} + + # check if the log metrics are configured for VPC network changes + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514897777777|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_network\" AND (protoPayload.methodName:\"compute.networks.insert\" OR protoPayload.methodName:\"compute.networks.patch\" OR protoPayload.methodName:\"compute.networks.delete\" OR protoPayload.methodName:\"compute.networks.removePeering\" OR protoPayload.methodName:\"compute.networks.addPeering\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": false, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-1\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514898888888|test-log-metric-2", + "displayName": "test-log-metric-2", + }, + "filter": "resource.type=gce_network\nAND protoPayload.methodName:\"compute.networks.insert\"", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy-3", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-4", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/test-log-metric-2\""}}], + }, + ]}, + }, + { + "metadata": { + "id": "googlecloud-project|514899999999", + "displayName": "test-project-1", + }, + "cloudLogging": {"metrics": [{ + "metadata": { + "id": "googlecloud-cl-metric|514899999999|test-log-metric-1", + "displayName": "test-log-metric-1", + }, + "filter": "resource.type=\"gce_network\" AND (protoPayload.methodName:\"compute.networks.insert\" OR protoPayload.methodName:\"compute.networks.patch\" OR protoPayload.methodName:\"compute.networks.delete\" OR protoPayload.methodName:\"compute.networks.removePeering\" OR protoPayload.methodName:\"compute.networks.addPeering\")", + }]}, + "cloudMonitoring": {"policies": [ + { + "displayName": "test-alert-policy", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"cloudsql_database\" AND metric.type = \"cloudsql.googleapis.com/database/disk/bytes_used\""}}], + }, + { + "displayName": "test-alert-policy-2", + "enabled": true, + "conditions": [{"threshold": {"filter": "resource.type = \"logging_sink\" AND metric.type = \"logging.googleapis.com/user/dummy-test-log-metric-100\""}}], + }, + ]}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide.graphql new file mode 100644 index 0000000..4bd7384 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide.graphql @@ -0,0 +1,26 @@ +{ + googleCloud { + projects { + id + network { + vpcNetworks { + metadata { + id + } + firewallRules { + name + sourceRanges + direction + allowed { + ipProtocol + ports { + from + to + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide.rego new file mode 100644 index 0000000..34092d3 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide.rego @@ -0,0 +1,97 @@ +package policy.googlecloud.networking.fw_rule_iap + +import data.shisho +import future.keywords.every + +# The policy confirms that each VPC allows traffic to the following ports only from the IAP proxy and Google Health Check. +# - 80/tcp +# - 443/tcp +# +# If you want to restrict the access to other ports as well, you can add them to the `restriction_targets` list. +# When all ports should be protected, TBD!!! +restriction_targets := [ + { + "protocol": "tcp", + "port": 80, + }, + { + "protocol": "tcp", + "port": 443, + }, +] + +google_source_ranges := [ + "35.235.240.0/20", # IAP Proxy Addresses + "130.211.0.0/22", # Google Health Check + "35.191.0.0/16", # Google Health Check +] + +decisions[d] { + project := input.googleCloud.projects[_] + network := project.network.vpcNetworks[_] + + # List up all ingress rules from the firewall rules that may affect security of restriction targets. + related_rules := related_ingress_rules(network.firewallRules) + + # The VPC is allowed if and only if the rules allow traffic only from Google ranges. + allowed := allow_traffic_only_from_google_ranges(related_rules) + + d := shisho.decision.googlecloud.networking.fw_rule_iap({ + "allowed": allowed, + "subject": network.metadata.id, + "payload": shisho.decision.googlecloud.networking.fw_rule_iap_payload({"ingress_rules": related_rules}), + }) +} + +# Returns the ingress firewall rules that may affect security of apps served under `restriction_targets`. +related_ingress_rules(firewall_rules) = x { + x := [{"name": rule.name, "source_ranges": rule.sourceRanges, "allow_rules": related_allow_rules} | + # On each ingress rule ... + rule := firewall_rules[_] + rule.direction == "INGRESS" + + # confirm the rule may affect `restriction target`. + # Note: if `related_allow_rules` is empty, the `rule` does not affect security of `restriction_targets`. + related_allow_rules := [{"ip_protocol": allow_rule.ipProtocol, "port_ranges": allow_rule.ports} | + allow_rule := rule.allowed[_] + may_affect_restriction_target(allow_rule) + ] + count(related_allow_rules) > 0 + ] +} else = [] + +# An allow rule in a firewall rule affects security of restriction targets if and only if .... +may_affect_restriction_target(allow_rule) { + # For a restriction target ... + restriction_target := restriction_targets[_] + + # the rule uses the target protocol ... + restriction_target.protocol == allow_rule.ipProtocol + + # and the rule has a port range that includes the target port. + include_ports(allow_rule.ports, restriction_target.port) +} else := false + +# Note: a port range can be empty, and the rule allows all ports in that case. +# Reference: https://cloud.google.com/compute/docs/reference/rest/v1/firewalls and Shisho Cloud datasource GraphQL schema +include_ports(ports, p) { + range := ports[_] + + range.from <= p + p <= range.to +} else { + count(ports) == 0 +} else = false + +# Confirm all of the given rules have source ranges within Google ranges. +# +# Note: If no *allowing* firewall rule, the traffic will be blocked by *implied deny ingress rule*. +# That's why this function returns true even if `rules` is empty. +# https://cloud.google.com/firewall/docs/firewalls#default_firewall_rules +allow_traffic_only_from_google_ranges(rules) { + every rule in rules { + every range in rule.source_ranges { + net.cidr_contains(google_source_ranges[_], range) + } + } +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide_test.rego new file mode 100644 index 0000000..a48d7de --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/fw-rule-iap/decide_test.rego @@ -0,0 +1,175 @@ +package policy.googlecloud.networking.fw_rule_iap + +import data.shisho +import future.keywords + +test_whether_firewall_rules_for_iap_are_allowed if { + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 6 with input as {"googleCloud": {"projects": [{ + "id": "shisho-security-dev-tools", + "network": {"vpcNetworks": [ + # Within the Google ranges #1 + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777771"}, + "firewallRules": [{ + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [{ + "from": 80, + "to": 80, + }], + }], + "direction": "INGRESS", + "sourceRanges": ["35.235.240.0/20"], + }], + }, + # Within the Google ranges #2 + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777772"}, + "firewallRules": [{ + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [{ + "from": 80, + "to": 80, + }], + }], + "direction": "INGRESS", + "sourceRanges": ["35.235.240.1/32"], + }], + }, + # Out of the Google ranges, but it has no allow rules (-> implied rules will block traffic so it's okay) + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777773"}, + "firewallRules": [{ + "name": "test-firewall-rule-1", + "allowed": [], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }], + }, + # Out of the Google ranges, but it's on unrestricted ports + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777774"}, + "firewallRules": [{ + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [{ + "from": 1024, + "to": 65535, + }], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }], + }, + # Out of the Google ranges, but it's an EGRESS rule + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777775"}, + "firewallRules": [{ + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [{ + "from": 80, + "to": 80, + }], + }], + "direction": "EGRESS", + "sourceRanges": ["0.0.0.0/0"], + }], + }, + # Out of the Google ranges, but it's on unrestricted protocols + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777776"}, + "firewallRules": [{ + "name": "test-firewall-rule-6", + "allowed": [{ + "ipProtocol": "udp", + "ports": [{ + "from": 80, + "to": 80, + }], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }], + }, + ]}, + }]}} +} + +test_whether_firewall_rules_for_iap_are_denied if { + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{ + "id": "shisho-security-dev-tools", + "network": {"vpcNetworks": [ + # Out of the Google ranges + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777771"}, + "firewallRules": [{ + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [{ + "from": 80, + "to": 80, + }], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }], + }, + # Out of the Google ranges (without a specific port range) + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777772"}, + "firewallRules": [{ + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }], + }, + # Out of the Google ranges (multiple rules, only one violation) + { + "metadata": {"id": "googlecloud-nw-vpc-network|514897777777|3345992333817777771"}, + "firewallRules": [ + { + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [{ + "from": 80, + "to": 80, + }], + }], + "direction": "INGRESS", + "sourceRanges": ["35.235.240.0/20"], + }, + { + "name": "test-firewall-rule-1", + "allowed": [{ + "ipProtocol": "tcp", + "ports": [{ + "from": 80, + "to": 80, + }], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }, + ], + }, + ]}, + }]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide.graphql new file mode 100644 index 0000000..f01e47d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide.graphql @@ -0,0 +1,15 @@ +{ + googleCloud { + projects { + network { + vpcNetworks { + metadata { + id + displayName + } + subnetworkMode + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide.rego new file mode 100644 index 0000000..856f329 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide.rego @@ -0,0 +1,17 @@ +package policy.googlecloud.networking.legacy_network + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + vpcNetwork := project.network.vpcNetworks[_] + + d := shisho.decision.googlecloud.networking.legacy_network({ + "allowed": vpcNetwork.subnetworkMode != "LEGACY", + "subject": vpcNetwork.metadata.id, + "payload": shisho.decision.googlecloud.networking.legacy_network_payload({ + "name": vpcNetwork.metadata.displayName, + "subnetwork_mode": vpcNetwork.subnetworkMode, + }), + }) +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide_test.rego new file mode 100644 index 0000000..46bd47a --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/legacy-network/decide_test.rego @@ -0,0 +1,48 @@ +package policy.googlecloud.networking.legacy_network + +import data.shisho +import future.keywords + +test_whether_networks_are_not_legacy_networks if { + # check if the networks are not legacy networks + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"network": {"vpcNetworks": [ + { + "metadata": { + "id": "googlecloud-nw-vpc-network|514897777777|3345992333817777777", + "displayName": "test-network-1", + }, + "subnetworkMode": "AUTO", + }, + { + "metadata": { + "id": "googlecloud-nw-vpc-network|514898888888|8757077963368888888", + "displayName": "test-network-2", + }, + "subnetworkMode": "CUSTOM", + }, + ]}}]}} + + # check if the default networks exist on VPC Network list + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"network": {"vpcNetworks": [ + { + "metadata": { + "id": "googlecloud-nw-vpc-network|514897777777|3345992333817777777", + "displayName": "test-network-1", + }, + "subnetworkMode": "LEGACY", + }, + { + "metadata": { + "id": "googlecloud-nw-vpc-network|514898888888|8757077963368888888", + "displayName": "test-network-2", + }, + "subnetworkMode": "LEGACY", + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/manifest.yaml index f3ef2e3..ae69605 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/manifest.yaml @@ -45,7 +45,7 @@ jobs: input: schema: !include proxy-tls-policy/decide.graphql - id: rdp-access - name: Review the availability of the RDS access + name: Review the availability of the RDP access decide: rego: !include rdp-access/decide.rego with: @@ -80,3 +80,27 @@ jobs: values: [] input: schema: !include vpc-flow-log/decide.graphql + - id: fw-rule-iap + name: Review that Identity Aware Proxy (IAP) allows only traffic from Google IP addresses + decide: + rego: !include fw-rule-iap/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include fw-rule-iap/decide.graphql + - id: legacy-network + name: Review that legacy networks do not exist for older Google Cloud projects + decide: + rego: !include legacy-network/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include legacy-network/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide.rego index 25c573b..7f9417a 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide.rego +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide.rego @@ -50,9 +50,11 @@ range_includes(r, p) { false } -# if the IP protocol is "all" or "tcp", return true +# if the IP protocol is "all", "tcp", "sctp" return true allows_ssh_protocol(protocol) { protocol == "all" } else { protocol == "tcp" +} else { + protocol == "sctp" } else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide_test.rego index 1ffcc42..8458f14 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide_test.rego +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/networking/ssh-access/decide_test.rego @@ -12,7 +12,7 @@ test_whether_ssh_is_disabled_by_firewall_policies if { "id": "test-project-1", "network": {"vpcNetworks": [ { - # allowed (the ingerss is allowed, but tcp/22 itself is not allowed) + # allowed (the ingress is allowed, but tcp & sctp/22 itself is not allowed) "metadata": {"id": "googlecloud-nw-vpc-network|514893255555|56580586120417777"}, "firewallRules": [ { @@ -113,6 +113,15 @@ test_whether_ssh_is_disabled_by_firewall_policies if { "direction": "INGRESS", "sourceRanges": ["35.235.240.0/20"], }, + { + "network": "https://www.googleapis.com/compute/v1/projects/test-project-2/global/networks/test-3", + "allowed": [{ + "ipProtocol": "sctp", + "ports": [{"from": 22, "to": 22}], + }], + "direction": "INGRESS", + "sourceRanges": ["35.235.240.0/20"], + }, ], }]}, }, @@ -121,7 +130,7 @@ test_whether_ssh_is_disabled_by_firewall_policies if { count([d | decisions[d] not shisho.decision.is_allowed(d) - ]) == 2 with input as {"googleCloud": {"projects": [ + ]) == 4 with input as {"googleCloud": {"projects": [ { "id": "test-project-1", "network": {"vpcNetworks": [{ @@ -174,5 +183,46 @@ test_whether_ssh_is_disabled_by_firewall_policies if { ], }]}, }, + { + "id": "test-project-3", + "network": {"vpcNetworks": [{ + "metadata": {"id": "googlecloud-nw-vpc-network|514893255555|56580586120433333"}, + "firewallRules": [ + { + "network": "https://www.googleapis.com/compute/v1/projects/test-project-3/global/networks/test-1", + "allowed": [{ + "ipProtocol": "sctp", + "ports": [{"from": 22, "to": 22}], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }, + { + "network": "https://www.googleapis.com/compute/v1/projects/test-project-1/global/networks/test-2", + "allowed": [{ + "ipProtocol": "sctp", + "ports": [{"from": 0, "to": 22}], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }, + ], + }]}, + }, + { + "id": "test-project-4", + "network": {"vpcNetworks": [{ + "metadata": {"id": "googlecloud-nw-vpc-network|514893255555|56580586120444444"}, + "firewallRules": [{ + "network": "https://www.googleapis.com/compute/v1/projects/test-project-4/global/networks/test-1", + "allowed": [{ + "ipProtocol": "sctp", + "ports": [], + }], + "direction": "INGRESS", + "sourceRanges": ["0.0.0.0/0"], + }], + }]}, + }, ]}} } diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide.graphql new file mode 100644 index 0000000..c103e65 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLMySQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide.rego new file mode 100644 index 0000000..6ef150d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide.rego @@ -0,0 +1,23 @@ +package policy.googlecloud.sql.instance_mysql_local_infile + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := local_infile_state(instance.settings.databaseFlags) + d := shisho.decision.googlecloud.sql.instance_mysql_local_infile({ + "allowed": state == "off", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_mysql_local_infile_payload({"local_infile_state": state}), + }) +} + +local_infile_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "local_infile" + + # the default is "on" + flag.value == "off" +} else = "on" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide_test.rego new file mode 100644 index 0000000..2de598f --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-local-infile/decide_test.rego @@ -0,0 +1,77 @@ +package policy.googlecloud.sql.instance_mysql_local_infile + +import data.shisho +import future.keywords + +test_whether_instance_mysql_local_infile_is_off_for_mysql_instances_of_cloud_sql if { + # check if `local_infile` is set to `off` for all MySQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-1", + "displayName": "test-mysql-1", + }, + "settings": {"databaseFlags": [{ + "name": "local_infile", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-2", + "displayName": "test-mysql-2", + }, + "settings": {"databaseFlags": [{ + "name": "local_infile", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-3", + "displayName": "test-mysql-3", + }, + "settings": {"databaseFlags": [{ + "name": "local_infile", + "value": "off", + }]}, + }, + ]}}]}} + + # check if `local_infile` is not set to `off` for all MySQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-1", + "displayName": "test-mysql-1", + }, + "settings": {"databaseFlags": [{ + "name": "local_infile", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-2", + "displayName": "test-mysql-2", + }, + "settings": {"databaseFlags": [{ + "name": "local_infile", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-3", + "displayName": "test-mysql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide.graphql new file mode 100644 index 0000000..c103e65 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLMySQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide.rego new file mode 100644 index 0000000..c7b982b --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide.rego @@ -0,0 +1,23 @@ +package policy.googlecloud.sql.instance_mysql_show_database + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := skip_show_database_state(instance.settings.databaseFlags) + d := shisho.decision.googlecloud.sql.instance_mysql_show_database({ + "allowed": state == "on", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_mysql_show_database_payload({"skip_show_database_state": state}), + }) +} + +skip_show_database_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "skip_show_database" + + # the default is "off" + flag.value == "on" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide_test.rego new file mode 100644 index 0000000..8cbf823 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-mysql-show-database/decide_test.rego @@ -0,0 +1,77 @@ +package policy.googlecloud.sql.instance_mysql_show_database + +import data.shisho +import future.keywords + +test_whether_skip_show_database_is_off_for_mysql_instances_of_cloud_sql if { + # check if `skip_show_database` is set to `on` for all MySQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-1", + "displayName": "test-mysql-1", + }, + "settings": {"databaseFlags": [{ + "name": "skip_show_database", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-2", + "displayName": "test-mysql-2", + }, + "settings": {"databaseFlags": [{ + "name": "skip_show_database", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-3", + "displayName": "test-mysql-3", + }, + "settings": {"databaseFlags": [{ + "name": "skip_show_database", + "value": "on", + }]}, + }, + ]}}]}} + + # check if `skip_show_database` is not set to `on` for all MySQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-1", + "displayName": "test-mysql-1", + }, + "settings": {"databaseFlags": [{ + "name": "skip_show_database", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-2", + "displayName": "test-mysql-2", + }, + "settings": {"databaseFlags": [{ + "name": "skip_show_database", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-mysql-3", + "displayName": "test-mysql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide.rego new file mode 100644 index 0000000..833cf3a --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide.rego @@ -0,0 +1,24 @@ +package policy.googlecloud.sql.instance_postgresql_log_connections + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := log_connections_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_postgresql_log_connections({ + "allowed": state == "on", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_log_connections_payload({"log_connections_state": state}), + }) +} + +log_connections_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "log_connections" + + # the default is "off" + flag.value == "on" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide_test.rego new file mode 100644 index 0000000..f9f3e73 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-connections/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_postgresql_log_connections + +import data.shisho +import future.keywords + +test_whether_log_connections_is_off_for_postgresql_instances_of_cloud_sql if { + # check if `log_connections` is set to `on` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_connections", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_connections", + "value": "on", + }]}, + }, + ]}}]}} + + # check if `log_connections` is not set to `on` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_connections", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_connections", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide.rego new file mode 100644 index 0000000..9ef82cb --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide.rego @@ -0,0 +1,24 @@ +package policy.googlecloud.sql.instance_postgresql_log_disconnections + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := log_disconnections_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_postgresql_log_disconnections({ + "allowed": state == "on", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_log_disconnections_payload({"log_disconnections_state": state}), + }) +} + +log_disconnections_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "log_disconnections" + + # the default is "off" + flag.value == "on" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide_test.rego new file mode 100644 index 0000000..1d835d4 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-disconnections/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_postgresql_log_disconnections + +import data.shisho +import future.keywords + +test_whether_log_connections_is_off_for_postgresql_instances_of_cloud_sql if { + # check if `log_disconnections` is set to `on` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_disconnections", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_disconnections", + "value": "on", + }]}, + }, + ]}}]}} + + # check if `log_disconnections` is not set to `on` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_disconnections", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_disconnections", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide.rego new file mode 100644 index 0000000..bc4b846 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide.rego @@ -0,0 +1,27 @@ +package policy.googlecloud.sql.instance_postgresql_log_error_verbosity + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + allowed_states := ["default", "verbose"] + state := log_error_verbosity_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_postgresql_log_error_verbosity({ + "allowed": allowed_states[_] == state, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_log_error_verbosity_payload({"log_error_verbosity_state": state}), + }) +} + +# available values are "terse", "default" and "verbose" (see https://cloud.google.com/sql/docs/postgres/flags#postgres-l) +# 1. `default` logs the basic error information +# 2. `terse` excludes the logging of DETAIL, HINT, QUERY, and CONTEXT error information +# 3. `verbose` output includes the SQLSTATE error code and the source code file name, function name, and line number that generated the error +# the default value is "default" +log_error_verbosity_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "log_error_verbosity" +} else = "default" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide_test.rego new file mode 100644 index 0000000..7ae9115 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-error-verbosity/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_postgresql_log_error_verbosity + +import data.shisho +import future.keywords + +test_whether_log_error_verbosity_is_off_for_postgresql_instances_of_cloud_sql if { + # check if `log_error_verbosity` is set to `verbose` or `default` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_error_verbosity", + "value": "verbose", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_error_verbosity", + "value": "default", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `log_error_verbosity` is not set to `verbose` or `default` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_error_verbosity", + "value": "terse", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_error_verbosity", + "value": "terse", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide.rego new file mode 100644 index 0000000..c94894b --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide.rego @@ -0,0 +1,24 @@ +package policy.googlecloud.sql.instance_postgresql_centralized_logging + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := instance_postgresql_centralized_logging_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_postgresql_centralized_logging({ + "allowed": state == "on", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_centralized_logging_payload({"pgaudit_enabled": state}), + }) +} + +instance_postgresql_centralized_logging_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "cloudsql.enable_pgaudit" + + # the default is "off" + flag.value == "on" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide_test.rego new file mode 100644 index 0000000..ef0377f --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-hostname/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_postgresql_centralized_logging + +import data.shisho +import future.keywords + +test_whether_cloudsql_enable_pgaudit_is_on_for_postgresql_instances_of_cloud_sql if { + # check if `cloudsql.enable_pgaudit` is set to `on` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "cloudsql.enable_pgaudit", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "cloudsql.enable_pgaudit", + "value": "on", + }]}, + }, + ]}}]}} + + # check if `cloudsql.enable_pgaudit` is not set to `on` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "cloudsql.enable_pgaudit", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "cloudsql.enable_pgaudit", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide.rego new file mode 100644 index 0000000..1870910 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide.rego @@ -0,0 +1,26 @@ +package policy.googlecloud.sql.instance_postgresql_log_min_duration_statement + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + ms := log_min_duration_statement(instance.settings.databaseFlags) + + # the range: -1 - 2147483647 ms + # the default value: -1 + allowed := ms == -1 + + d := shisho.decision.googlecloud.sql.instance_postgresql_log_min_duration_statement({ + "allowed": allowed, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_log_min_duration_statement_payload({"milliseconds_of_log_min_duration_statement": ms}), + }) +} + +log_min_duration_statement(database_flags) := x { + flag := database_flags[_] + flag.name == "log_min_duration_statement" + x := to_number(flag.value) +} else = -1 diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide_test.rego new file mode 100644 index 0000000..8d48a53 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-duration-statement/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_postgresql_log_min_duration_statement + +import data.shisho +import future.keywords + +test_whether_log_min_duration_statement_is_warning_for_postgresql_instances_of_cloud_sql if { + # check if `log_min_duration_statement` is set to `-1` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_duration_statement", + "value": "-1", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_duration_statement", + "value": "-1", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `log_min_messages` is not set to `warning` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_duration_statement", + "value": "20", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_duration_statement", + "value": "30", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide.rego new file mode 100644 index 0000000..b09704c --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide.rego @@ -0,0 +1,36 @@ +package policy.googlecloud.sql.instance_postgresql_log_min_error_statement + +import data.shisho + +allowed_states := ["error"] + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := log_min_error_statement_state(instance.settings.databaseFlags) + + # available values are "debug5", "debug4", "debug3", "debug2", "debug1", "info", "notice", "warning", "error", "log", "fatal" and "panic" (see https://cloud.google.com/sql/docs/postgres/flags#postgres-l) + # 1. `debug1 .. debug5` provides successively-more-detailed information for use by developers + # 2. `info` provides information implicitly requested by the user, e.g., output from VACUUM VERBOSE + # 3. `notice` provides information that might be helpful to users, e.g., notice of truncation of long identifiers + # 4. `warning` provides warnings of likely problems, e.g., COMMIT outside a transaction block + # 1. `error` reports an error that caused the current command to abort + # 2. `log` reports information of interest to administrators, e.g., checkpoint activity + # 3. `fatal` reports an error that caused the current session to abort + # 4. `panic` reports an error that caused all database sessions to abort + # the 'error' is recommended, but please update the `allowed_states` depending on your organization's logging policy such as `allowed_states := ["error", "warning"]` + allowed := allowed_states[_] == state + + d := shisho.decision.googlecloud.sql.instance_postgresql_log_min_error_statement({ + "allowed": allowed, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_log_min_error_statement_payload({"log_min_error_statement_state": state}), + }) +} + +# the default value is "error" +log_min_error_statement_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "log_min_error_statement" +} else = "error" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide_test.rego new file mode 100644 index 0000000..0192274 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-error-statement/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_postgresql_log_min_error_statement + +import data.shisho +import future.keywords + +test_whether_log_min_error_statement_is_error_for_postgresql_instances_of_cloud_sql if { + # check if `log_min_error_statement` is set to `error` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_error_statement", + "value": "error", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_error_statement", + "value": "error", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `log_min_error_statement` is not set to `error` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_error_statement", + "value": "panic", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_error_statement", + "value": "warning", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide.rego new file mode 100644 index 0000000..24c457c --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide.rego @@ -0,0 +1,36 @@ +package policy.googlecloud.sql.instance_postgresql_log_min_messages + +import data.shisho + +allowed_states := ["warning"] + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := log_min_messages_state(instance.settings.databaseFlags) + + # available values are "debug5", "debug4", "debug3", "debug2", "debug1", "info", "notice", "warning", "error", "log", "fatal" and "panic" (see https://cloud.google.com/sql/docs/postgres/flags#postgres-l) + # 1. `debug1 .. debug5` provides successively-more-detailed information for use by developers + # 2. `info` provides information implicitly requested by the user, e.g., output from VACUUM VERBOSE + # 3. `notice` provides information that might be helpful to users, e.g., notice of truncation of long identifiers + # 4. `warning` provides warnings of likely problems, e.g., COMMIT outside a transaction block + # 1. `error` reports an error that caused the current command to abort + # 2. `log` reports information of interest to administrators, e.g., checkpoint activity + # 3. `fatal` reports an error that caused the current session to abort + # 4. `panic` reports an error that caused all database sessions to abort + # the 'warning' is recommended, but please update the `allowed_states` depending on your organization's logging policy such as `allowed_states := ["error", "warning"]` + allowed := state == allowed_states[_] + + d := shisho.decision.googlecloud.sql.instance_postgresql_log_min_messages({ + "allowed": allowed, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_log_min_messages_payload({"log_min_messages_state": state}), + }) +} + +# the default value is "warning" +log_min_messages_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "log_min_messages" +} else = "warning" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide_test.rego new file mode 100644 index 0000000..b53bc6d --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-min-messages/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_postgresql_log_min_messages + +import data.shisho +import future.keywords + +test_whether_log_min_messages_is_warning_for_postgresql_instances_of_cloud_sql if { + # check if `log_min_messages` is set to `warning` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_messages", + "value": "warning", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_messages", + "value": "warning", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `log_min_messages` is not set to `warning` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_messages", + "value": "panic", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_min_messages", + "value": "error", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide.graphql new file mode 100644 index 0000000..53d35dd --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLPostgreSQLInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide.rego new file mode 100644 index 0000000..a25ead3 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide.rego @@ -0,0 +1,32 @@ +package policy.googlecloud.sql.instance_postgresql_log_statement + +import data.shisho + +allowed_states := ["ddl"] + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := log_statement_state(instance.settings.databaseFlags) + + # available values are "none", "ddl", "mod" and "all" (see https://cloud.google.com/sql/docs/postgres/flags#postgres-l) + # 1. `all` logs all statements + # 2. `ddl` logs all data definition statements, such as CREATE, ALTER, and DROP statements + # 3. `mod` logs all ddl statements, plus data-modifying statements such as INSERT, UPDATE, DELETE, TRUNCATE, and COPY FROM. PREPARE, EXECUTE, and EXPLAIN ANALYZE statements are also logged if their contained command is of an appropriate type + # 4. `none` does not log any statements + # the 'ddl' is recommended, but please update the `allowed_states` depending on your organization's logging policy such as `allowed_states := ["ddl", "mod", "all"]` + allowed := state == allowed_states[_] + + d := shisho.decision.googlecloud.sql.instance_postgresql_log_statement({ + "allowed": allowed, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_postgresql_log_statement_payload({"log_statement_state": state}), + }) +} + +# the default value is "none" +log_statement_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "log_statement" +} else = "none" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide_test.rego new file mode 100644 index 0000000..6652468 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-postgresql-log-statement/decide_test.rego @@ -0,0 +1,87 @@ +package policy.googlecloud.sql.instance_postgresql_log_statement + +import data.shisho +import future.keywords + +test_whether_log_statement_is_none_for_postgresql_instances_of_cloud_sql if { + # check if `log_statement` is set to `ddl` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_statement", + "value": "ddl", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_statement", + "value": "ddl", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": [{ + "name": "log_statement", + "value": "ddl", + }]}, + }, + ]}}]}} + + # check if `log_statement` is not set to `ddl` for all PostgreSQL instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 4 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-1", + "displayName": "test-postgresql-1", + }, + "settings": {"databaseFlags": [{ + "name": "log_statement", + "value": "none", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-2", + "displayName": "test-postgresql-2", + }, + "settings": {"databaseFlags": [{ + "name": "log_statement", + "value": "all", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-3", + "displayName": "test-postgresql-3", + }, + "settings": {"databaseFlags": []}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-postgresql-4", + "displayName": "test-postgresql-4", + }, + "settings": {"databaseFlags": [{ + "name": "log_statement", + "value": "mod", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide.graphql new file mode 100644 index 0000000..dadbcfc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLSQLServerInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide.rego new file mode 100644 index 0000000..da36032 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide.rego @@ -0,0 +1,22 @@ +package policy.googlecloud.sql.instance_sqlserver_3625_trace_flag + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := trace_flag_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_sqlserver_3625_trace_flag({ + "allowed": state == "off", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_sqlserver_3625_trace_flag_payload({"trace_flag_state": state}), + }) +} + +# the default is "off" +trace_flag_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "3625" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide_test.rego new file mode 100644 index 0000000..212ef1b --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-3625-trace-flag/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_sqlserver_3625_trace_flag + +import data.shisho +import future.keywords + +test_whether_3625_trace_flag_is_off_for_sqlserver_instances_of_cloud_sql if { + # check if `3625` is off for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "3625", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "3625", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-3", + "displayName": "test-sqlserver-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `3625` is not off for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "3625", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "3625", + "value": "on", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide.graphql new file mode 100644 index 0000000..dadbcfc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLSQLServerInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide.rego new file mode 100644 index 0000000..81ad170 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide.rego @@ -0,0 +1,22 @@ +package policy.googlecloud.sql.instance_sqlserver_contained_db_authentication + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := contained_db_authentication_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_sqlserver_contained_db_authentication({ + "allowed": state == "off", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_sqlserver_contained_db_authentication_payload({"contained_db_authentication_state": state}), + }) +} + +# the default is "off" +contained_db_authentication_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "contained database authentication" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide_test.rego new file mode 100644 index 0000000..0dd0ea8 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-contained-db-authentication/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_sqlserver_contained_db_authentication + +import data.shisho +import future.keywords + +test_whether_contained_db_authentication_is_off_for_sqlserver_instances_of_cloud_sql if { + # check if `contained_db_authentication` is off for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "contained database authentication", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "contained database authentication", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-3", + "displayName": "test-sqlserver-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `contained_db_authentication` is not off for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "contained database authentication", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "contained database authentication", + "value": "on", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide.graphql new file mode 100644 index 0000000..dadbcfc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLSQLServerInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide.rego new file mode 100644 index 0000000..090691b --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide.rego @@ -0,0 +1,22 @@ +package policy.googlecloud.sql.instance_sqlserver_cross_db_ownership_chaining + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := instance_sqlserver_cross_db_ownership_chaining_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_sqlserver_cross_db_ownership_chaining({ + "allowed": state == "off", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_sqlserver_cross_db_ownership_chaining_payload({"cross_db_ownership_chaining_state": state}), + }) +} + +# the default value is "off" +instance_sqlserver_cross_db_ownership_chaining_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "cross db ownership chaining" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide_test.rego new file mode 100644 index 0000000..93dd504 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-cross-db-ownership-chaining/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_sqlserver_cross_db_ownership_chaining + +import data.shisho +import future.keywords + +test_whether_cross_db_ownership_chaining_is_off_for_sqlserver_instances_of_cloud_sql if { + # check if `cross db ownership chaining` is set to `off` for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "cross db ownership chaining", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "cross db ownership chaining", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-3", + "displayName": "test-sqlserver-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `cross db ownership chaining` is set to `off` for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "cross db ownership chaining", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "cross db ownership chaining", + "value": "on", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide.graphql new file mode 100644 index 0000000..dadbcfc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLSQLServerInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide.rego new file mode 100644 index 0000000..a8abf91 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide.rego @@ -0,0 +1,22 @@ +package policy.googlecloud.sql.instance_sqlserver_external_scripts + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := external_scripts_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_sqlserver_external_scripts({ + "allowed": state == "off", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_sqlserver_external_scripts_payload({"external_scripts_state": state}), + }) +} + +# the default value is "off" +external_scripts_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "external scripts enabled" +} else = "off" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide_test.rego new file mode 100644 index 0000000..f7d24c2 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-external-scripts/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_sqlserver_external_scripts + +import data.shisho +import future.keywords + +test_whether_external_scripts_enabled_is_off_for_sqlserver_instances_of_cloud_sql if { + # check if `external scripts enabled` is set to `off` for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "external scripts enabled", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "external scripts enabled", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-3", + "displayName": "test-sqlserver-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `external scripts enabled` is set to `off` for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "external scripts enabled", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "external scripts enabled", + "value": "on", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide.graphql new file mode 100644 index 0000000..dadbcfc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLSQLServerInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide.rego new file mode 100644 index 0000000..36f5bfa --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide.rego @@ -0,0 +1,22 @@ +package policy.googlecloud.sql.instance_sqlserver_remote_access + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + state := remote_access_state(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_sqlserver_remote_access({ + "allowed": state == "off", + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_sqlserver_remote_access_payload({"remote_access_state": state}), + }) +} + +# the default is "on" +remote_access_state(database_flags) := flag.value { + flag := database_flags[_] + flag.name == "remote access" +} else = "on" diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide_test.rego new file mode 100644 index 0000000..6d380f4 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-remote-access/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_sqlserver_remote_access + +import data.shisho +import future.keywords + +test_whether_remote_access_is_off_for_sqlserver_instances_of_cloud_sql if { + # check if `remote access` is off for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "remote access", + "value": "off", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "remote access", + "value": "off", + }]}, + }, + ]}}]}} + + # check if `remote access` is not off for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "remote access", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "remote access", + "value": "on", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-3", + "displayName": "test-sqlserver-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide.graphql new file mode 100644 index 0000000..dadbcfc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLSQLServerInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide.rego new file mode 100644 index 0000000..f30a77c --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide.rego @@ -0,0 +1,27 @@ +package policy.googlecloud.sql.instance_sqlserver_user_connections + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + amount := user_connections_state(instance.settings.databaseFlags) + + # the maximum is 32,767 user connections + # the default is 0 and it means that the maximum (32,767) user connections are allowed + allowed := amount == 0 + + d := shisho.decision.googlecloud.sql.instance_sqlserver_user_connections({ + "allowed": allowed, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_sqlserver_user_connections_payload({"maximum_user_connections": amount}), + }) +} + +# the default value is "off" +user_connections_state(database_flags) := x { + flag := database_flags[_] + flag.name == "user connections" + x := to_number(flag.value) +} else = 0 diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide_test.rego new file mode 100644 index 0000000..7244566 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-connections/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_sqlserver_user_connections + +import data.shisho +import future.keywords + +test_whether_user_connections_is_zero_for_sqlserver_instances_of_cloud_sql if { + # check if `user connections` is set to 0 for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "user connections", + "value": "0", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "user connections", + "value": "0", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-3", + "displayName": "test-sqlserver-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `user connections` is set to 0 for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "user connections", + "value": "10", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "user connections", + "value": "200", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide.graphql new file mode 100644 index 0000000..dadbcfc --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide.graphql @@ -0,0 +1,22 @@ +{ + googleCloud { + projects { + cloudSql { + instances { + ... on GoogleCloudSQLSQLServerInstance { + metadata { + id + displayName + } + settings { + databaseFlags { + name + value + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide.rego new file mode 100644 index 0000000..05abda7 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide.rego @@ -0,0 +1,21 @@ +package policy.googlecloud.sql.instance_sqlserver_user_options + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + instance := project.cloudSql.instances[_] + + configured := user_options_configured(instance.settings.databaseFlags) + + d := shisho.decision.googlecloud.sql.instance_sqlserver_user_options({ + "allowed": configured == false, + "subject": instance.metadata.id, + "payload": shisho.decision.googlecloud.sql.instance_sqlserver_user_options_payload({"user_options_configured": configured}), + }) +} + +user_options_configured(database_flags) { + flag := database_flags[_] + flag.name == "user options" +} else = false diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide_test.rego new file mode 100644 index 0000000..26ff8ee --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/instance-sqlserver-user-options/decide_test.rego @@ -0,0 +1,67 @@ +package policy.googlecloud.sql.instance_sqlserver_user_options + +import data.shisho +import future.keywords + +test_whether_user_options_is_configured_for_sqlserver_instances_of_cloud_sql if { + # check if `user options` is not configured for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 3 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "user connections", + "value": "0", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "user connections", + "value": "0", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-3", + "displayName": "test-sqlserver-3", + }, + "settings": {"databaseFlags": []}, + }, + ]}}]}} + + # check if `user options` is configured for all SQL Server instances of Google Cloud SQL + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [{"cloudSql": {"instances": [ + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-1", + "displayName": "test-sqlserver-1", + }, + "settings": {"databaseFlags": [{ + "name": "user options", + "value": "2", + }]}, + }, + { + "metadata": { + "id": "googlecloud-sql-instance|514897777777|test-sqlserver-2", + "displayName": "test-sqlserver-2", + }, + "settings": {"databaseFlags": [{ + "name": "user options", + "value": "1", + }]}, + }, + ]}}]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/manifest.yaml index a6f4c90..a4c1d2d 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/sql/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/sql/manifest.yaml @@ -56,3 +56,207 @@ jobs: values: [] input: schema: !include instance-public-ip/decide.graphql + - id: instance-sqlserver-cross-db-ownership-chaining + name: Review that the cross_db_ownership_chaining_state database flag for a Cloud SQL for SQL Server instance is set to off + decide: + rego: !include instance-sqlserver-cross-db-ownership-chaining/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-sqlserver-cross-db-ownership-chaining/decide.graphql + - id: instance-postgresql-log-statement + name: Review that the log_statement database flag for a Cloud SQL for PostgreSQL instance is set appropriately + decide: + rego: !include instance-postgresql-log-statement/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-statement/decide.graphql + - id: instance-mysql-local-infile + name: Review that the local_infile database flag for a Cloud SQL for MySQL instance is set to off + decide: + rego: !include instance-mysql-local-infile/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-mysql-local-infile/decide.graphql + - id: instance-postgresql-log-min-error-statement + name: Review that the log_min_error_statement database flag for a Cloud SQL for PostgreSQL instance is set to error or stricter + decide: + rego: !include instance-postgresql-log-min-error-statement/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-min-error-statement/decide.graphql + - id: instance-sqlserver-external-scripts + name: Review that the external_scripts_state enabled database flag for a Cloud SQL for SQL Server instance is set to off + decide: + rego: !include instance-sqlserver-external-scripts/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-sqlserver-external-scripts/decide.graphql + - id: instance-sqlserver-contained-db-authentication + name: Review that the contained_db_authentication_state database flag for a Cloud SQL for SQL Server instance is set to off + decide: + rego: !include instance-sqlserver-contained-db-authentication/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-sqlserver-contained-db-authentication/decide.graphql + - id: instance-sqlserver-3625-trace-flag + name: Review that the 3625 (trace flag) database flag for all Cloud SQL for SQL Server instances is set to off + decide: + rego: !include instance-sqlserver-3625-trace-flag/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-sqlserver-3625-trace-flag/decide.graphql + - id: instance-postgresql-log-disconnections + name: Review that the log_disconnections database flag for a Cloud SQL for PostgreSQL instance is set to On + decide: + rego: !include instance-postgresql-log-disconnections/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-disconnections/decide.graphql + - id: instance-postgresql-log-min-duration-statement + name: Review that the log_min_duration_statement database flag for a Cloud SQL for PostgreSQL instance is set to -1 + decide: + rego: !include instance-postgresql-log-min-duration-statement/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-min-duration-statement/decide.graphql + - id: instance-sqlserver-user-connections + name: Review that the maximum_user_connections database flag for a Cloud SQL for SQL Server instance is set to a non-limiting value + decide: + rego: !include instance-sqlserver-user-connections/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-sqlserver-user-connections/decide.graphql + - id: instance-postgresql-log-connections + name: Review that the log_connections database flag for a Cloud SQL for PostgreSQL instance is set to On + decide: + rego: !include instance-postgresql-log-connections/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-connections/decide.graphql + - id: instance-postgresql-log-error-verbosity + name: Review that the log_error_verbosity database flag for a Cloud SQL for PostgreSQL instance is set to DEFAULT or stricter + decide: + rego: !include instance-postgresql-log-error-verbosity/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-error-verbosity/decide.graphql + - id: instance-sqlserver-user-options + name: Review that the user_options_configured database flag for a Cloud SQL for SQL Server instance is not configured + decide: + rego: !include instance-sqlserver-user-options/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-sqlserver-user-options/decide.graphql + - id: instance-postgresql-log-hostname + name: Review log_hostname database flag for a Cloud SQL for PostgreSQL instance is set to on + decide: + rego: !include instance-postgresql-log-hostname/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-hostname/decide.graphql + - id: instance-sqlserver-remote-access + name: Review that the remote_access_state database flag for a Cloud SQL for SQL Server instance is set to off + decide: + rego: !include instance-sqlserver-remote-access/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-sqlserver-remote-access/decide.graphql + - id: instance-mysql-show-database + name: Review that the skip_show_database database flag for a Cloud SQL for MySQL instance is set to on + decide: + rego: !include instance-mysql-show-database/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-mysql-show-database/decide.graphql + - id: instance-postgresql-log-min-messages + name: Review that the log_min_messages database flag for a Cloud SQL for PostgreSQL instance is set to at least warning + decide: + rego: !include instance-postgresql-log-min-messages/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include instance-postgresql-log-min-messages/decide.graphql diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/storage/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/storage/manifest.yaml index 3f4ea17..cdd0163 100644 --- a/workflows/cis-benchmark/googlecloud-v1.3.0/storage/manifest.yaml +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/storage/manifest.yaml @@ -20,7 +20,7 @@ jobs: values: [] input: schema: !include bucket-accessibility/decide.graphql - - id: bucket-uniform-access + - id: bucket-uniform-bucket-level-access name: Review buckets' uniform bucket level access decide: rego: !include bucket-uniform-bucket-level-access/decide.rego diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide.graphql b/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide.graphql new file mode 100644 index 0000000..1bdd0fb --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide.graphql @@ -0,0 +1,15 @@ +{ + googleCloud { + projects { + metadata { + id + displayName + } + accessApproval { + settings { + name + } + } + } + } +} \ No newline at end of file diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide.rego new file mode 100644 index 0000000..70c83a0 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide.rego @@ -0,0 +1,15 @@ +package policy.googlecloud.support.access_approval + +import data.shisho + +decisions[d] { + project := input.googleCloud.projects[_] + + allowed := project.accessApproval.settings != null + + d := shisho.decision.googlecloud.support.access_approval({ + "allowed": allowed, + "subject": project.metadata.id, + "payload": shisho.decision.googlecloud.support.access_approval_payload({"enabled": allowed}), + }) +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide_test.rego b/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide_test.rego new file mode 100644 index 0000000..9009be1 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/support/access-approval/decide_test.rego @@ -0,0 +1,48 @@ +package policy.googlecloud.support.access_approval + +import data.shisho +import future.keywords + +test_whether_access_approval_is_enabled_for_projects if { + # check if the access approval is enabled for projects + count([d | + decisions[d] + shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "accessApproval": {"settings": {"name": "test-approval-settings-1"}}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "accessApproval": {"settings": {"name": "test-approval-settings-2"}}, + }, + ]}} + + # check if the access approval is enabled for projects + count([d | + decisions[d] + not shisho.decision.is_allowed(d) + ]) == 2 with input as {"googleCloud": {"projects": [ + { + "metadata": { + "id": "googlecloud-project|514897777777", + "displayName": "test-project-1", + }, + "accessApproval": {"settings": null}, + }, + { + "metadata": { + "id": "googlecloud-project|514898888888", + "displayName": "test-project-2", + }, + "accessApproval": {"settings": null}, + }, + ]}} +} diff --git a/workflows/cis-benchmark/googlecloud-v1.3.0/support/manifest.yaml b/workflows/cis-benchmark/googlecloud-v1.3.0/support/manifest.yaml new file mode 100644 index 0000000..7699f47 --- /dev/null +++ b/workflows/cis-benchmark/googlecloud-v1.3.0/support/manifest.yaml @@ -0,0 +1,22 @@ +version: 0.1.0 + +id: "prebundle-googlecloud-support" +name: "Prebundle: Review Google Cloud support posture" + +triggers: + schedule: + - cron: "0 */1 * * *" + +jobs: + - id: access-approval + name: Review Access Approval is enabled + decide: + rego: !include access-approval/decide.rego + with: + resource_exceptions: + type: resource_exception + multiple: true + description: A special list of resource exceptions + values: [] + input: + schema: !include access-approval/decide.graphql diff --git a/workflows/csp/aws-fsbp/cloudfront/manifest.yaml b/workflows/csp/aws-fsbp/cloudfront/manifest.yaml index 8342b20..34f2809 100644 --- a/workflows/csp/aws-fsbp/cloudfront/manifest.yaml +++ b/workflows/csp/aws-fsbp/cloudfront/manifest.yaml @@ -42,7 +42,7 @@ jobs: values: [] input: schema: !include origin-transport/decide.graphql - - id: "origin-access-control" + - id: "origin-access" name: Review origin access control configuration decide: rego: !include origin-access/decide.rego diff --git a/workflows/csp/aws-fsbp/elb/alb-header/decide.graphql b/workflows/csp/aws-fsbp/elb/alb-header/decide.graphql index ce34134..b113c13 100644 --- a/workflows/csp/aws-fsbp/elb/alb-header/decide.graphql +++ b/workflows/csp/aws-fsbp/elb/alb-header/decide.graphql @@ -15,6 +15,11 @@ query { dropInvalidHeaderFields desyncMitigationMode } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/elb/alb-header/decide.rego b/workflows/csp/aws-fsbp/elb/alb-header/decide.rego index de504cf..2a61816 100644 --- a/workflows/csp/aws-fsbp/elb/alb-header/decide.rego +++ b/workflows/csp/aws-fsbp/elb/alb-header/decide.rego @@ -10,7 +10,7 @@ decisions[d] { allowed := enabled d := shisho.decision.aws.alb.invalid_header_handling({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, lb), "subject": lb.metadata.id, "payload": shisho.decision.aws.alb.invalid_header_handling_payload({"invalid_header_mitigation_enabled": enabled}), }) @@ -35,3 +35,17 @@ allowed_desync_mitigation_mode(m) { } else { m == "DEFENSIVE" } 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/elb/deletion-protection/decide.graphql b/workflows/csp/aws-fsbp/elb/deletion-protection/decide.graphql index 4145ff6..55e0a5d 100644 --- a/workflows/csp/aws-fsbp/elb/deletion-protection/decide.graphql +++ b/workflows/csp/aws-fsbp/elb/deletion-protection/decide.graphql @@ -14,6 +14,11 @@ query { attributes { enabledDeletionProtection } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/elb/deletion-protection/decide.rego b/workflows/csp/aws-fsbp/elb/deletion-protection/decide.rego index f7aaffc..8e68ae8 100644 --- a/workflows/csp/aws-fsbp/elb/deletion-protection/decide.rego +++ b/workflows/csp/aws-fsbp/elb/deletion-protection/decide.rego @@ -10,8 +10,22 @@ decisions[d] { allowed := enabled d := shisho.decision.aws.alb.delete_protection({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, lb), "subject": lb.metadata.id, "payload": shisho.decision.aws.alb.delete_protection_payload({"deletion_protection_enabled": enabled}), }) } + +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/elb/logging/decide.graphql b/workflows/csp/aws-fsbp/elb/logging/decide.graphql index 0748218..c8b340e 100644 --- a/workflows/csp/aws-fsbp/elb/logging/decide.graphql +++ b/workflows/csp/aws-fsbp/elb/logging/decide.graphql @@ -18,6 +18,11 @@ query { s3BucketPrefix } } + + tags { + key + value + } } } } diff --git a/workflows/csp/aws-fsbp/elb/logging/decide.rego b/workflows/csp/aws-fsbp/elb/logging/decide.rego index c2f2086..110a17c 100644 --- a/workflows/csp/aws-fsbp/elb/logging/decide.rego +++ b/workflows/csp/aws-fsbp/elb/logging/decide.rego @@ -10,7 +10,7 @@ decisions[d] { allowed := access_log.enabled d := shisho.decision.aws.alb.logging({ - "allowed": allowed, + "allowed": allow_if_excluded(allowed, lb), "subject": lb.metadata.id, "payload": shisho.decision.aws.alb.logging_payload({ "log_enabled": access_log.enabled, @@ -23,3 +23,17 @@ decisions[d] { string_or_default(s, d) = s { s != null } else := d + +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/elb/manifest.yaml b/workflows/csp/aws-fsbp/elb/manifest.yaml index 226fd00..9e5a6a2 100644 --- a/workflows/csp/aws-fsbp/elb/manifest.yaml +++ b/workflows/csp/aws-fsbp/elb/manifest.yaml @@ -8,7 +8,7 @@ triggers: - cron: "0 */1 * * *" jobs: - - id: alb-logging + - id: logging name: Review logging decide: rego: !include logging/decide.rego diff --git a/workflows/notification/slack/security-ja/manifest.yaml b/workflows/notification/slack/security-ja/manifest.yaml index 7508f0a..4b178f4 100644 --- a/workflows/notification/slack/security-ja/manifest.yaml +++ b/workflows/notification/slack/security-ja/manifest.yaml @@ -24,7 +24,7 @@ triggers: - awaiting_review jobs: - - id: triage-status-updation + - id: triage name: Notify triage status updation notify: rego: !include triage/notify.rego