Hi everyone! I'm just starting to use Semgrep for scanning our Terraform code. The built-in rules are great, but I need to write a few custom ones for our AWS setup.
I'm a bit stuck on the basics of writing my own rule. For example, I want to flag any S3 bucket that doesn't have `block_public_acls` set to true. I tried writing a rule but it's not catching what I think it should.
Here's my attempt so far:
```yaml
rules:
- id: no-block-public-acls
patterns:
- pattern: |
resource "aws_s3_bucket_public_access_block" "..." {
...
}
- pattern-not: |
resource "aws_s3_bucket_public_access_block" "..." {
block_public_acls = true
}
message: S3 bucket should have block_public_acls enabled.
severity: WARNING
```
Is this the right approach? Any tips on how to structure these custom rules for Terraform? Also, what's the best way to test them locally before pushing to the registry?
Good start, but I think your pattern is looking for the resource block itself, not the missing property. Try using a single pattern with a metavariable. Something like:
pattern: resource "aws_s3_bucket_public_access_block" $NAME { ... }
pattern-not: block_public_acls = true
This way it finds the resource and then flags if that specific line isn't there. The metavariable $NAME helps anchor it.
For testing, run `semgrep --config your-rule.yaml /path/to/test-terraform` locally. I keep a small folder of "bad" test files to verify catches. Also, watch your indentation in the YAML, it can be finicky. Happy to look at your next draft!
Keep automating!
You're overcomplicating it. The ellipsis operator ... matches anything, so your pattern-not is also matching the resource declaration itself. It'll never fire.
For a missing property, you need patterns-inside with a metavariable, not pattern-not on the whole block. Something like:
patterns:
pattern-inside: resource "aws_s3_bucket_public_access_block" $NAME { ... }
pattern-not: block_public_acls = true
And test locally with a dummy file that's definitely wrong. The registry's validation is too loose.
Your stack is too complicated.
Hold on, you're throwing away the resource pattern entirely? If the pattern-inside anchors to the block but the pattern-not just looks for the property anywhere inside, won't it incorrectly flag a file that has the property set correctly *and* also has another, completely unrelated resource without it? The ellipsis in the pattern-inside is too permissive.
The real mischief is thinking you can catch a missing property with just a pair of simple patterns. Terraform's nested blocks and dynamic arguments make this a proper logic puzzle. You're basically trying to prove a negative, which Semgrep's patterns alone are annoyingly bad at.
Maybe we're all barking up the wrong tree and should just use a Rego rule for OPA instead. Just a thought
🤷
Ooh, good catch about the over-permissive ellipsis! That's a classic trap. For Terraform, I often use `pattern-either` with explicit property patterns instead of a single `pattern-not`. It's a bit more verbose, but safer.
I don't think you need to jump to Rego yet, though. Semgrep's `metavariable-pattern` can help scope the `pattern-not` more tightly. You anchor the property search to the specific block using a metavariable from the `patterns-inside`.
Happy customers, happy life.
`metavariable-pattern` can work, but you still have to be careful about the matching scope. The gotcha I've seen is that it anchors the search to the entire block's content, which means a `pattern-not` on a property that appears inside a nested block (like a `dynamic` block that also sets `block_public_acls`) will still match the wrong thing. Semgrep's Terraform parser treats `dynamic` blocks as separate AST nodes, so the metavariable from the outer `resource` block won't reach into them unless you explicitly recurse.
I'd rather just write two separate rules: one that catches the resource block without the property at all, and another that catches the block with the property set to `false`. That avoids the nesting ambiguity entirely. It's more lines of YAML, but `pattern-either` with two explicit `pattern` blocks is less prone to false negatives than a single `pattern-not` with a metavariable scoping trick.
Have you actually tested `metavariable-pattern` on a Terraform file with a `dynamic` access block? The docs suggest it works, but the parser's behavior with nested blocks has given me headaches before.
Your fancy demo doesn't scale.