diff --git a/src/tirith/tui/examples/06-rds-not-public/about.md b/src/tirith/tui/examples/06-rds-not-public/about.md new file mode 100644 index 00000000..cb80a7a4 --- /dev/null +++ b/src/tirith/tui/examples/06-rds-not-public/about.md @@ -0,0 +1,17 @@ +A single check for publicly accessible databases, and two instances that disagree. + +Databases holding production data should rarely accept connections from the open internet. In AWS Terraform, whether an `aws_db_instance` is reachable from outside its VPC is governed by the `publicly_accessible` boolean flag. + +`NotEquals` tests that the attribute is anything other than `true`. If `publicly_accessible` is set to `true`, the check fails. If set to `false` (or omitted, which defaults to private in AWS), the check passes. + +The input plan contains two database instances: +- `aws_db_instance.private_db`, configured with `publicly_accessible = false` (passes). +- `aws_db_instance.public_db`, configured with `publicly_accessible = true` (fails). + +Because one instance violates the policy, the overall evaluation fails. In the results view, notice how the resource address immediately identifies which instance caused the failure. + +**Things to try** + +- In `input.json`, change `public_db`'s `publicly_accessible` to `false`. Re-evaluate and watch the verdict turn green. +- Remove `publicly_accessible` from `public_db` entirely. The check still passes because missing attributes evaluate to `null`, which is not equal to `true`. +- In `policy.json`, change `NotEquals` with `true` to `Equals` with `false`. See what happens when `publicly_accessible` is omitted — `null` does not equal `false`, showing why `NotEquals: true` is often the safer choice for boolean security flags. diff --git a/src/tirith/tui/examples/06-rds-not-public/input.json b/src/tirith/tui/examples/06-rds-not-public/input.json new file mode 100644 index 00000000..509d25c6 --- /dev/null +++ b/src/tirith/tui/examples/06-rds-not-public/input.json @@ -0,0 +1,50 @@ +{ + "format_version": "1.2", + "terraform_version": "1.5.7", + "resource_changes": [ + { + "address": "aws_db_instance.private_db", + "mode": "managed", + "type": "aws_db_instance", + "name": "private_db", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "identifier": "private-db", + "publicly_accessible": false, + "allocated_storage": 20, + "instance_class": "db.t3.micro" + }, + "after_unknown": { + "arn": true + } + } + }, + { + "address": "aws_db_instance.public_db", + "mode": "managed", + "type": "aws_db_instance", + "name": "public_db", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "identifier": "public-db", + "publicly_accessible": true, + "allocated_storage": 20, + "instance_class": "db.t3.micro" + }, + "after_unknown": { + "arn": true + } + } + } + ] +} diff --git a/src/tirith/tui/examples/06-rds-not-public/policy.json b/src/tirith/tui/examples/06-rds-not-public/policy.json new file mode 100644 index 00000000..2720fbe2 --- /dev/null +++ b/src/tirith/tui/examples/06-rds-not-public/policy.json @@ -0,0 +1,24 @@ +{ + "meta": { + "version": "v1", + "required_provider": "stackguardian/terraform_plan", + "name": "No public RDS instances", + "severity": "high" + }, + "evaluators": [ + { + "id": "rds_is_not_public", + "description": "RDS instances must not be publicly accessible", + "provider_args": { + "operation_type": "attribute", + "terraform_resource_type": "aws_db_instance", + "terraform_resource_attribute": "publicly_accessible" + }, + "condition": { + "type": "NotEquals", + "value": true + } + } + ], + "eval_expression": "rds_is_not_public" +} diff --git a/tests/tui/test_examples.py b/tests/tui/test_examples.py index 0e1f8547..f8762a32 100644 --- a/tests/tui/test_examples.py +++ b/tests/tui/test_examples.py @@ -28,6 +28,7 @@ "03-cost-ceiling": results.PASSED, "04-block-destroy": results.FAILED, "05-kubernetes-probes": results.FAILED, + "06-rds-not-public": results.FAILED, } ALL_EXAMPLES = examples.load_examples()