Featured image of post The security service I had to switch off

The security service I had to switch off

A while back I wrote about hardening the account that would hold the signing key, and one line in it has aged badly. “GuardDuty is already looking,” I wrote: the account watched from day one, threat detection on before the key even arrives. Then I went to apply that baseline to a brand-new account, and GuardDuty wasn’t looking at all, because the account wouldn’t let it.

What the baseline switches on

The baseline runs a threat-detection module that turns on three AWS services: GuardDuty, Security Hub, and IAM Access Analyzer. Each sits behind an enable_* toggle, all defaulting to on, which is the right default. An account about to hold something sensitive should be watched, flagged and analysed from the start. The hardening post’s whole argument was that you fit the locks before you move the valuables in, and I stand by it. The gap I hadn’t accounted for is the one between a posture you can design and a posture a fresh account will actually run on its first day.

SubscriptionRequiredException

GuardDuty and Security Hub are first-party AWS services, but they are not on by default. They have to be activated for the account before anything can configure them. Point OpenTofu at a brand-new account that has never had them switched on and you don’t get a security baseline, you get a SubscriptionRequiredException and a failed apply. The locks you carefully specced won’t fit, because the door hasn’t been registered as a door yet.

I’d actually met this exact wall from another direction: an aws-nuke dry-run on a fresh account throws screenfuls of the same exception for every service you’ve never enabled. Same root cause, different tool. A new AWS account is a quieter, emptier place than the console makes it look.

There’s a second, duller reason too, and it belongs in the open: GuardDuty and Security Hub both cost money once their free trial lapses, and the budget for this account wasn’t in place yet. Enabling a service you can’t yet afford to keep on is its own small mistake.

Switching two watchers back off, on purpose

So I did the thing that felt wrong and was right. I switched two of them off, and wrote the reason straight into the module call:

# GuardDuty + Security Hub are deferred. The account's first-time
# service activation is pending (it returns SubscriptionRequiredException)
# and the services carry an ongoing cost beyond their free trial.
# Re-enable by flipping both to true (or deleting these lines) once
# activation clears and the budget is in place.
enable_guardduty   = false
enable_securityhub = false

Two things make that a deferral rather than a retreat. First, it’s scoped: Access Analyzer needs no subscription and costs nothing, so it stays on. The account isn’t unwatched, it’s watched by the part that can watch it right now. Second, it carries its own undo. The comment is the re-enable instructions, the toggles sit right there, and flipping them back is a one-line change the day activation clears and the budget lands. A disabled control with a written path back is a deferral. A disabled control with no note is a hole someone finds in a year.

Why this lives in the module, not in a hack

I could only do this cleanly because the module exposes each service as its own toggle, and it only gained that recently; the previous version was all-or-nothing on threat detection. Granular enable_guardduty and enable_securityhub flags are exactly what let you say “this account, these two, not yet” without forking the module or commenting resources out. It’s the difference between a baseline you adapt per account and one you fight.

The honest version of “secure by default”

The hardening post wasn’t wrong. It was idealised. Secure-by-default is the right aim, and on a mature account the baseline goes on clean. On a brand-new one, reality intrudes: services that must be activated before they can be configured, costs that need a budget before they can be incurred. The honest response isn’t to pretend the posture is fully live when it isn’t. It’s to enable what the account will take, defer what it won’t, write down precisely why and how to finish, and leave Access Analyzer watching in the meantime. GuardDuty will be looking soon enough. It just wasn’t looking on day one, and saying so is better than a comment-free enable = true that quietly errored on every apply.

Built with Hugo
Theme Stack designed by Jimmy