skip to content

Artifact Under Evaluation

A gate reads a document, not your intent — a plan with change actions, a template full of unresolved references, or an unrendered chart. The input's shape decides which rules are writable at all.

on this pageshow

explore

questions

17

In a Terraform plan JSON, which section shows that one resource references another?

level: juniorimportance: must knowfreq 58%

answer

  1. values and links live apart
  2. expressions, not attributes
  3. look in the configuration section
  4. references carry addresses, ids do not
  5. then join by address

basics

~10 s

The configuration section. Each resource there carries an expressions object whose references list the addresses it points at. The planned_values section holds only each resource's resolved attribute values and records no links between resources.

solid answer

~40 s

Relationships live in `configuration`, not in `planned_values`. Under `configuration.root_module.resources`, every resource has an `expressions` object with one entry per argument, and an argument written as a reference appears as `{"references": ["aws_security_group.web.id", "aws_security_group.web"]}` — resource addresses, not values. `planned_values` gives you each resource's post-apply attributes keyed by address, and nothing else; the instance's `vpc_security_group_ids` there would be provider ids like `sg-0ab…`, which are opaque strings you cannot look up in the document, and for a group this run is creating there is no id printed at all. So any rule whose subject is "the security group this instance attaches to" is a two-step job: read the reference out of `configuration`, then join by address back into `planned_values` to read the target's attributes.

go deeper

for a junior

Be ready to name the two sections and say what each is for: configuration holds expressions and references, planned_values holds resolved attribute values per resource address. Knowing that links and values live apart is the whole point here.

for a middle

Explain why a provider id in planned_values is useless as a join key, and show the three-step shape of a cross-resource rule: find the subject, read the reference, join by address. Mention that references may point at variables or module outputs, not only resources.

for a senior

Demonstrate that you know what a green result means when the reference is a literal id or the target lives outside this configuration. An interviewer wants to hear you distinguish 'no violation found' from 'the rule could not evaluate its subject'.

for a principal

Own the consequence for a control catalogue: which controls are expressible against a single plan at all, and how you keep the gate's stated coverage honest when some of them are not. The framing to defend is that the artifact's shape, not your intent, sets the limit.

## The plan is several views, not one picture A Terraform plan rendered as JSON is not a single model of your infrastructure. It is a set of parallel views of the same run, and each answers a different question. Two of them matter for cross-resource rules: - **`planned_values`** — what each resource will look like after apply. Under `planned_values.root_module.resources` you get one entry per resource instance, each with an `address` (`aws_instance.web[0]`), a `type`, a `name`, and a `values` object holding the resolved attributes. - **`configuration`** — your code, normalised into JSON. Under `configuration.root_module.resources` you get one entry per resource *block* (not per instance), each with an unindexed `address`, a `mode` (`managed` or `data`), and an `expressions` object with one entry per argument. The distinction that decides what rules you can write: **`planned_values` records values; `configuration` records links.** ## Why the values view cannot answer a relationship question Take a real control: *an instance tagged `tier = data` must not attach a security group that allows egress to `0.0.0.0/0`.* Two resources, and the violation exists only in the pair. From `planned_values` you can read the instance's `tags`, and you can separately read each security group's `egress` blocks. What you cannot read is **which group this instance attaches to**. The instance's `vpc_security_group_ids` attribute holds provider identifiers — opaque strings like `sg-0ab12…` — and an identifier is not an address. There is no index in the document that maps `sg-0ab12…` back to `aws_security_group.web`, so you have nothing to look up. And when the same run is creating that group, the identifier does not exist yet at all, so the attribute carries no usable value either. Written only against `planned_values`, the rule collapses into one of two useless shapes: flag *every* group with open egress (which fires on the NAT and load-balancer groups nobody objects to), or flag *every* data-tier instance (which tells you nothing about its network exposure). ## The link is an expression, and expressions are in `configuration` In the configuration view the same argument looks like this: ``` "expressions": { "vpc_security_group_ids": { "references": ["aws_security_group.web.id", "aws_security_group.web"] } } ``` An expression entry is one of two things. Either a **`constant_value`**, when the argument was written as a literal, or a **`references`** array, when it was written as a reference to something else. The array typically carries both the full traversal (`aws_security_group.web.id`) and the bare resource address (`aws_security_group.web`), because both are legitimate ways to name what was traversed. The bare address is the one you want: it is exactly the key you will match on in `planned_values`. References are not always to resources. `var.subnet_id`, `local.sg`, `module.network.security_group_id` and `data.aws_ami.base.id` all appear in the same array shape, so a rule that assumes every reference names a managed resource in this module will trip over the first indirection. ## The shape of every cross-resource rule Once you know where the two halves live, the pattern is always the same three steps: 1. Find the subject in `configuration` — the resource whose argument creates the relationship. 2. Pull the target's address out of that argument's `references`. 3. Join by address into `planned_values` and read the target's attributes. Then assert over the pair. The join key is a string address, and getting that string right — indices, module prefixes — is where the work actually is. ## The case with no reference at all If someone wrote `vpc_security_group_ids = ["sg-0ab12…"]`, the expression is a `constant_value` and there is no `references` entry to follow. The group is a real thing in the account, it is doing real damage, and it is simply not in this document — not as a managed resource, not as anything. A rule that starts by reading `references` finds nothing, produces no violation, and reports green. That is the more general limit of a plan gate: the document describes **one root configuration's run**, so a subject that lives in another configuration, or was created by hand, is not there to be judged.

  • What exactly does a references array contain for one argument?
    Usually both the full traversal that was written and the bare resource address it started from — for example `aws_security_group.web.id` alongside `aws_security_group.web`. Strip to the bare address before joining, since that is the form `planned_values` uses. References can also name non-resources: `var.sg_id`, `local.groups`, `module.network.sg_id`, or a `data.` address, so check what kind of thing you landed on before assuming you have a resource.
  • What if the attribute was written as a hard-coded id string instead of a reference?
    Then the configuration entry is a `constant_value` and there is no `references` array at all. The target resource is not declared in this configuration, so there is nothing in the document to join to and nothing to evaluate. A rule that only walks references will silently produce no violation — which looks identical to a pass. Detect that case explicitly and report it as unevaluable rather than letting it fall through.
  • Does the configuration section also tell you the attribute's final value?
    Only when it was a literal, as a `constant_value`. Anything computed, defaulted by the provider, or derived from a variable or another resource is resolved into `planned_values`, not into `configuration`. So the two sections are complementary rather than redundant: read links from one and values from the other.

planned_values is a phone book: every entry's details, no calls. configuration is the call log: who dialled whom, no details. A question about a conversation needs both, joined by name.

saying these in an interview costs you the question

  • Assumes planned_values records links between resources
  • Treats a provider id as a joinable resource address
  • Expects final attribute values in the configuration section
  • Believes the plan contains every resource in the account
  • Writes the rule against one resource and calls the pair covered

context

open as a page

In a Terraform plan JSON, where do resources declared inside a module appear to a policy rule?

level: juniorimportance: must knowfreq 66%

basics

~20 s

The plan is flattened. Every resource a module declares is expanded into the plan's resource lists under a module-qualified address such as module.network.aws_s3_bucket.flow_logs, so ordinary resource rules fire on module-created resources without knowing modules exist.

open as a page

Why must a Helm chart be rendered before conftest can evaluate its Kubernetes manifests?

level: juniorimportance: must knowfreq 64%

basics

~10 s

A chart's templates are Go template text, not valid YAML, and fields hidden behind conditionals only appear once values are applied. Render the chart first, then evaluate the manifests it produces.

open as a page

In a Terraform plan JSON, how do `planned_values` and `resource_changes` differ for a policy rule?

level: juniorimportance: must knowfreq 72%

basics

~20 s

planned_values is the whole post-apply state, every managed resource including untouched ones. resource_changes is the per-resource diff, carrying the action and the before and after values. A rule over planned_values judges the estate; a rule over resource_changes judges this run.

open as a page

In a Terraform plan, what does an attribute marked "known after apply" mean for a policy rule?

level: juniorimportance: must knowfreq 68%

basics

~20 s

It means the value does not exist yet: the provider or cloud only produces it when the resource is actually created, so the plan holds a placeholder. A policy rule evaluating that attribute has nothing real to test.

open as a page

A Terraform plan rule matching only the `create` action let a database retention downgrade ship. Why?

level: middleimportance: must knowfreq 62%

basics

~20 s

Lowering a backup window on an existing database is planned as an in-place edit, so change.actions is ["update"], not ["create"]. The filter never matched, so the rule never ran. Match when the actions array contains create or update.

open as a page

Why does a rule denying deprecated TLS policies quietly pass a listener whose value is unknown until apply?

level: middleimportance: must knowfreq 52%

basics

~20 s

A value that does not exist matches nothing. The rule hunts for a forbidden TLS policy, finds no value at all under that attribute, and reports no violation — a pass that looks identical in CI to a genuine one.

open as a page

A Helm chart passes conftest on your laptop but the CI policy gate blocks the same commit - how do you diagnose it?

level: seniorimportance: must knowfreq 57%

basics

~20 s

The two runs evaluated different documents. Compare the rendered output, not the chart: the values files, set flags, release name and subchart versions used by each render decide whether the fields the rule wants exist at all.

open as a page

In Terraform plan JSON, how do you join a configuration reference back to its planned resource?

level: middleimportance: should knowfreq 50%

basics

~20 s

Strip the reference down to its bare resource address, then look that address up among the planned resources. The two sides do not match literally: configuration addresses carry no count or for_each index, and module resources are nested differently in each section.

open as a page

How would you gate a Terraform plan so every module call uses an approved source at a pinned version?

level: middleimportance: should knowfreq 55%

basics

~20 s

Read the plan's configuration section rather than its expanded resources: walk each entry under the root module's module_calls, match the source string against an allowlist the platform team owns, and require an exact version rather than a floating range or a mutable reference.

open as a page

In a CloudFormation template scanned by cfn-guard, what does a rule see when a property is set by !Ref or !If?

level: middleimportance: should knowfreq 46%

basics

~10 s

It sees the intrinsic itself, as data: a Ref or Fn::If node, not a resolved value. Nothing resolves parameters or conditions before deployment, so an equality test against a literal never matches.

open as a page

A policy gate denies a Terraform plan over a resource generated three modules deep — how do you resolve it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Translate the address back to the top-level module call the developer can actually edit, then fix it where it is fixable: a module input, a newer pinned version, or an upstream change. Attach any exception to the module and version, not to one plan.

open as a page

Your encryption-at-rest rule reads a key ARN unknown until apply: block, defer, or constrain the input?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Decide per control, not globally: blocking every unknown is safest but taxes teams with failures they cannot act on, deferring the assertion leaves a window where non-compliant infrastructure exists, and constraining the module input removes the unknown wherever you own the code.

open as a page

A change-scoped Terraform plan gate never sees a legacy database. How do you close that gap?

level: principalimportance: should knowfreq 44%

basics

~20 s

Keep the blocking rule scoped to changed resources, and add a scheduled evaluation over the full recorded state that reports rather than blocks. Give each finding an owner and a date; widen the blocking rule once the backlog is drained.

open as a page

Your conftest rule requiring a PodDisruptionBudget for every Deployment never fires - why?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

conftest evaluates each YAML document in the stream separately, so a rule can never see a Deployment and a PodDisruptionBudget at the same time. Combine the inputs into one evaluation, or the rule stays permanently undefined.

open as a page

A log-retention rule's subject lives in another root module's plan — how do you gate it?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Usually you do not gate it here. When the control's subject is missing from this plan, either constrain the module input so it becomes visible, or re-home the control where the subject exists. Approximating it is worse than not enforcing it.

open as a page

In a Terraform plan, how do you gate a change that lowers a database's backup retention window?

level: seniorimportance: nice to knowfreq 36%

basics

~20 s

Compare both sides of the diff. Deny when change.after's retention is lower than change.before's, so an already sub-standard database can be edited for unrelated reasons but never made worse. Creates have no before, so apply the absolute minimum.

open as a page