All articles
Automation
Guardrails
S3
IAM
Security Operations

Say Yes, Then Clean Up: Guardrails With an Expiry Date on AWS

Blocking every risky change slows teams down, and alerting on it gets ignored. A third option works better for temporary things like public buckets, open ports and test instances — allow them, and undo them automatically when their time is up.

Trust Partners Security Team 6 min read

Most cloud security programs have two tools for risky changes: prevent them or alert on them.

Prevention (an SCP, a permission boundary, a policy-as-code check in the pipeline) is strong but blunt. It says no to the developer who needs to share a dataset with a partner for an afternoon, and to the engineer who opens SSH to debug a broken instance at 2 a.m. They file a ticket, wait, and learn that security is the team that slows things down.

Alerting is the opposite. It says yes to everything and sends a message. The message goes to a channel with four hundred other messages. The bucket stays public for eleven months.

There is a third option, and for a large class of changes it is the right one: say yes, with an expiry date. Let people make the change, then undo it automatically when it has lived longer than it should — unless someone has explicitly said it should stay.

Temporary by nature

Look at the changes that cause the most incidents and the most friction, and many of them share a property: they are meant to be temporary.

Change Why people make it What usually happens
S3 bucket made public Share files with a customer or partner Nobody closes it after the handover
Security group opened to 0.0.0.0/0 Debug a connection, run a demo The rule outlives the debugging session by years
Test EC2 instance Try a new AMI, reproduce a bug Runs all month, found on the bill
IAM access key A script, a CI job, a one-off migration Never rotated, leaked in a repository three years later
Large instance for a load test Measure performance Left running after the report is written

For each of these, "never allowed" is wrong and "allowed forever" is wrong. The right policy has a time limit.

The pattern: match, wait, warn, act

An expiring guardrail is a scheduled check with four parts.

1. Match — a condition on the resource's current state, read from an inventory rather than from the event that created it: bucket without Block Public Access, security group with internet ingress, instance tagged temporary, active key created more than 90 days ago.

2. Wait — a grace period measured from the first time the resource matched, not from when it was created. A bucket created in January and opened today gets its full 24 hours. If the resource stops matching (someone closes the bucket), its clock resets.

3. Warn — when a resource first matches, tell the people who can act: "bucket partner-exports will be closed at 14:00 tomorrow unless it is tagged public-approved." The warning is what makes the guardrail feel fair. Nobody is surprised.

4. Act — when the grace period is over, apply the fix: block public access, revoke the internet rule, stop the instance, deactivate the key. Record what was done and how to undo it.

Two more pieces make it safe to run unattended:

  • Exemption tags. public-approved, public-ingress-approved, service-account. An exemption is a visible, auditable decision attached to the resource itself, not a line in a wiki. Review them quarterly.
  • Dry run first. Every new guardrail starts by reporting what it would do. Watch it for a week, fix the scope, then let it act.

Four guardrails worth having

1. Public buckets for 24 hours, at most

  • Match: S3 buckets without all four Block Public Access settings.
  • Wait: 24 hours, with a warning when the bucket first matches.
  • Exempt: public-approved (the static website bucket, the public dataset).
  • Act: turn on Block Public Access. Snapshot the previous configuration so it can be restored in one click.

This covers the most common data exposure in AWS without forbidding the legitimate use. Sharing with a partner still works — for a day.

2. Ports opened to the internet close themselves

  • Match: security groups with ingress from 0.0.0.0/0 or ::/0.
  • Wait: 4 hours.
  • Exempt: public-ingress-approved (the load balancer's group on 443).
  • Act: revoke only the internet-facing rules; keep narrower rules; record the removed rules so they can be re-authorized.

Four hours is long enough to debug and short enough that the rule is gone before the next internet-wide scan finds it twice.

3. Test instances turn themselves off

  • Match: running instances tagged temporary, or with an expires-on tag holding a date in the past.
  • Wait: 8 hours for temporary; none for an expired date.
  • Act: stop, not terminate. The disk survives, so the owner can start it again; the bill stops.

The expires-on tag turns lifetime into self-service: the person launching the instance decides how long it lives, and the guardrail keeps the promise.

4. Access keys retire on schedule

  • Match: active access keys created more than 90 days ago.
  • Wait: 7 days, with a warning on the first day.
  • Exempt: users tagged service-account that you have reviewed on purpose.
  • Act: deactivate the key. Deactivation is reversible; deletion is not.

Key rotation is a control most frameworks ask for (CIS AWS Foundations Benchmark 1.14, for one) and almost nobody enforces, because enforcement means breaking a script. A seven-day warning turns "break it" into "rotate it".

Designing the details

Measure grace from the first match, and keep the clock. A scheduler that only looks at the current state ("is it public now?") acts on a bucket that became public five minutes ago. A scheduler that looks at creation time acts on an old bucket the moment it is opened. Remember when each resource started matching, and forget it when it stops.

Read fresh state before acting. An inventory collected last night does not know about the exemption tag someone added an hour ago. Refresh the resource type before each run, and if the refresh fails, do nothing.

Unknown is not "untagged". If a bucket's tags cannot be read (a bucket policy denying the scanner, for example), treat it as unknown and leave it alone. Otherwise an exemption silently fails exactly where it matters.

Prefer reversible actions. Stop instead of terminate, deactivate instead of delete, block public access instead of deleting the bucket policy. The guardrail will occasionally be wrong; make being wrong cheap.

Tell people how to opt out. Every warning should name the exemption tag. If the only way to keep a resource is to ask the security team, you have rebuilt the ticket queue.

Rolling it out

  1. Start with one guardrail in dry-run mode — public buckets is a good first one — and look at what it would have closed.
  2. Tag the legitimate exceptions. Each tag is a decision someone made, visible on the resource.
  3. Turn on warnings, then switch the guardrail to act. Announce it once, with the exemption tag in the message.
  4. Add the next guardrail. Review exemptions every quarter.

How AWS Cloud Governance helps

AWS Cloud Governance runs these guardrails as scheduled tasks, inside your AWS account:

  • Presets for public buckets, internet-open security groups, temporary and expired instances, and old access keys, each a few clicks from running.
  • Grace periods measured from the first match, per resource, with the list of resources in their grace period and the time each will be acted on.
  • Warnings to email, Slack or Microsoft Teams when a resource starts matching, naming the exemption tags.
  • Fresh inventory: every run refreshes the resource type first and does nothing if that fails. Resources whose tags cannot be read never count as untagged.
  • Dry run, approval or automatic per task, and every action recorded with a one-click rollback.

Scheduled tasks: a public-bucket guardrail with its grace period and the resources waiting on it

See pricing or start a free 3-month trial at https://trustpartners.tech.