{"id":552,"date":"2026-08-24T12:00:00","date_gmt":"2026-08-24T09:00:00","guid":{"rendered":"https:\/\/john-nessime.com\/blog\/?p=552"},"modified":"2026-09-14T16:13:09","modified_gmt":"2026-09-14T13:13:09","slug":"aws-account-health-check","status":"publish","type":"post","link":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/","title":{"rendered":"The AWS Account Health Check I Run Before Quoting a Project"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">&#8220;It&#8217;s a small AWS account, nothing complicated, we just need someone to tidy it up.&#8221; That sentence has preceded more scope disasters than any other I know of in freelance cloud work, and it&#8217;s almost never a lie. The person saying it believes it, because the parts of an AWS account that make work expensive are the parts nobody looks at.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The expensive stuff is not the running instances. It&#8217;s the access keys nobody can attribute, the CloudTrail trail that quietly stopped delivering months ago, the Terraform state that no longer matches reality, and the region somebody spun a proof of concept up in two years ago. None of that comes up on a kickoff call. All of it comes up in week three, after you&#8217;ve quoted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So I run an AWS account health check before I give anyone a number. It&#8217;s read-only, it takes a couple of hours, and it&#8217;s the cheapest insurance in consulting. This post is the actual check: the six passes, the commands, what each finding does to the estimate, and what to do when the client won&#8217;t give you the access to run it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why the AWS account health check comes before the quote<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A quote is a bet on how much unknown there is. You&#8217;re pricing the work in the brief plus everything you&#8217;ll find while doing it, and the second half is invisible until you look. Two accounts can look identical and differ by a factor of three in effort. The difference is nearly always archaeology: how much has to be reverse engineered before you can safely change anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s a second reason that matters more than pricing. If you touch an account that already had a compromised key sitting in it, the incident becomes yours by proximity. Establishing what the account looked like before you arrived isn&#8217;t paranoia. It&#8217;s the only version of events you&#8217;ll be able to evidence later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The access I ask for, and what a refusal tells me<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I ask for a cross-account IAM role that the client creates and my account assumes. Not an IAM user. Not keys in an email. A role, with an external ID the client generates.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The external ID solves one specific problem. Without it, a trust policy that names my account can be used by anyone who talks me into assuming a role on their behalf, and I end up acting inside a third party&#8217;s account without knowing it. AWS calls this the confused deputy problem. It&#8217;s the piece of cross-account setup people skip most often.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On that role I ask for two AWS managed policies: <code>SecurityAudit<\/code> and <code>ViewOnlyAccess<\/code>. The pairing is the point. <code>ViewOnlyAccess<\/code> lets you enumerate resources across services, so you can see what exists. <code>SecurityAudit<\/code> lets you read configuration and policy documents, so you can see how it&#8217;s wired together. Neither alone gives you the picture, and neither lets you change anything. Billing is separate again: Cost Explorer read access has to be added explicitly and the feature has to be enabled in the account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The reply to that request tells you as much as the check does.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>&#8220;Here&#8217;s the role ARN and external ID&#8221; means somebody there understands AWS identity. Good sign.<\/li>\n\n<li>&#8220;I&#8217;ll just make you an admin user, easier&#8221; means nobody is managing access. Expect that looseness everywhere else.<\/li>\n\n<li>&#8220;Our previous developer set it up and we can&#8217;t reach him&#8221; means you&#8217;re inheriting credentials nobody can account for. Price the rotation work as its own line.<\/li>\n\n<li>&#8220;We&#8217;d rather not grant access before signing&#8221; is fair. Offer a paid discovery engagement. If they won&#8217;t pay for two hours of assessment, they won&#8217;t pay for the fix.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The six passes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each pass answers one question. Everything here is read-only, and the order matters, because each pass changes how you read the next.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pass 1: Who can do what<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm where you actually are first. That sounds trivial until you&#8217;re in an organization with a dozen similarly named accounts.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws sts get-caller-identity\naws iam list-account-aliases\naws organizations describe-organization<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the third command fails because the account isn&#8217;t in an organization, that&#8217;s a finding by itself. Standalone means no service control policies, no organization trail, and no central place to revoke anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then the credential surface. The summary gives counts; the credential report gives per-principal detail, including when each password and key was last used.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws iam get-account-summary\n\naws iam generate-credential-report\naws iam get-credential-report --query Content --output text | base64 --decode &gt; credential-report.csv<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The report generates asynchronously, so the second call may need a retry the first time. What I read from it: how many IAM users exist at all, how many have keys that have never been used, and how many have keys last used a long time ago that are still active.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An unused key is worse than a busy one. A key in daily use is attributable to something. A key last used eighteen months ago and still enabled belongs to a person or script nobody remembers, which means nobody will notice when someone else starts using it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then check whether anyone has ever asked this question before you:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws accessanalyzer list-analyzers<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">IAM Access Analyzer offers external access analyzers, which flag resources shared outside a zone of trust you define, and unused access analyzers, which surface unused roles, keys and passwords. Unused access analysis is billed per IAM role and user analyzed per month, so it isn&#8217;t free, but it answers the least privilege question far faster than reading policy documents by hand. An empty list means nobody has ever asked this account who can reach what.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Root closes the pass. In an AWS Organizations setup, centralized root access management lets the management account or a delegated IAM administrator remove root credentials from member accounts entirely, so there&#8217;s no root password, key or MFA device to manage per account. Live root credentials on every member account is a real finding, and one of the few security fixes that&#8217;s genuinely quick.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Pass test: you can name every principal that can reach production, and say when each last did.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pass 2: Would anyone notice<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This is the pass that most often changes my number, because it&#8217;s the failure that stays invisible until it isn&#8217;t. Logging that exists but has silently stopped working is more dangerous than no logging, since everybody believes they&#8217;re covered.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws cloudtrail describe-trails \n  --query 'trailList[].{Name:Name,MultiRegion:IsMultiRegionTrail,Org:IsOrganizationTrail}'\n\naws cloudtrail get-trail-status --name TRAIL_NAME_HERE\n\naws configservice describe-configuration-recorder-status<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first command tells you a trail is configured. The second tells you whether it&#8217;s delivering, which is the part that matters. A trail whose last delivery failed because someone edited the destination bucket policy still shows up in the console as a configured trail. The status call is where the truth is.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Multi-region matters for the same reason region sprawl does: a single-region trail records nothing about the region where somebody is quietly running compute. Check GuardDuty too, and how long logs are actually retained. Ninety days of retention against a one-year obligation is a project, not a task, and it belongs in the quote rather than in week four.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One naming trap worth knowing: AWS renamed the original Security Hub to Security Hub CSPM and launched a separate, newer service under the Security Hub name. When a client says &#8220;we have Security Hub,&#8221; ask which one, because the findings format and the configuration are not the same thing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Pass test: a trail is delivering right now, covers every region, and its logs are kept for at least as long as the business needs them.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pass 3: How wide the blast radius is<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">People forget regions. Not occasionally, constantly. A demo in Singapore, a test bucket in Ireland, a Lambda deployed from a tutorial that defaulted to us-east-1.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do\n  count=$(aws ec2 describe-instances --region \"$r\" \n    --query 'length(Reservations[].Instances[])' --output text)\n  echo \"$r $count\"\ndone<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Left without <code>--all-regions<\/code>, <code>describe-regions<\/code> returns the regions enabled for the account, which is exactly what you want. Adding the flag pulls in disabled regions too, and the per-region calls against those just error out.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run the same loop shape for anything carrying state or cost: RDS instances, load balancers, EBS volumes, NAT gateways. NAT gateways are the classic. They bill hourly whether or not anything routes through them, and a forgotten one in a region nobody visits is pure waste. Then the two exposure checks I never skip:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws ec2 describe-security-groups \n  --filters Name=ip-permission.cidr,Values=0.0.0.0\/0 \n  --query 'SecurityGroups[].{Id:GroupId,Name:GroupName,VpcId:VpcId}'\n\naws s3api list-buckets --query 'Buckets[].Name' --output text<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For each bucket that matters, check its public access block configuration and its policy. An open security group on 443 in front of a load balancer is fine. An open security group on a database port is a different conversation, and you want that conversation before you own the account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Pass test: you have a list of every region with resources in it, and nothing on that list surprises the client.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pass 4: Whether any of this is described in code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The question isn&#8217;t &#8220;do they use Terraform.&#8221; It&#8217;s whether the code still describes the account. Those diverge quietly. Somebody makes an urgent console change during an incident, never brings it back into code, and from then on the repo is fiction that plans cleanly right up until the day it destroys something.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -refresh-only\n\nterraform plan -detailed-exitcode<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The refresh-only plan shows where real infrastructure has drifted from state without proposing to change anything. The detailed exit code version is the one for scripts: it exits 0 for no changes, 1 for an error and 2 when there&#8217;s a diff, so you can gate on drift instead of reading output by eye.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Three things I want to know here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>What fraction of live resources are managed by code? Half a codebase is often worse than none, because it creates false confidence about which half.<\/li>\n\n<li>Where does state live and who can write to it? Remote state in a versioned, locked S3 bucket is a different risk profile from a state file on a laptop.<\/li>\n\n<li>Does the pipeline that applies changes still run? Credentials that expired six months ago mean everything since went in by hand.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Tagging belongs here too. With nothing tagged you can&#8217;t attribute cost, can&#8217;t scope a change safely, and can&#8217;t tell a production resource from an abandoned experiment. Untagged environments add hours to everything that follows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Pass test: a refresh-only plan comes back clean, or the drift is a short list you can read out loud.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pass 5: What shape the spend is in<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">I don&#8217;t care much about the total. I care about the shape: which services dominate, whether the curve is flat or climbing, and whether anything jumped without an explanation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws ce get-cost-and-usage \n  --time-period Start=$(date -d '6 months ago' +%Y-%m-01),End=$(date +%Y-%m-01) \n  --granularity MONTHLY \n  --metrics UnblendedCost \n  --group-by Type=DIMENSION,Key=SERVICE<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>date -d<\/code> syntax is GNU coreutils; on macOS use the BSD equivalent or a fixed start date. Worth knowing before you loop this: the Cost Explorer API bills per paginated request, unlike the console view, so a wide query across several dimensions is a small but real charge on the client&#8217;s bill. Keep it narrow and mention it in your notes.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Data transfer in the top three usually means an architecture problem rather than a rate problem. Cross-AZ chatter and NAT gateway egress are the usual suspects.<\/li>\n\n<li>A step change with no matching launch deserves a direct question. Sometimes it&#8217;s a feature. Sometimes it&#8217;s a runaway job. Occasionally it&#8217;s mining on a stolen key.<\/li>\n\n<li>No budgets and no anomaly monitors means nobody finds out about a spike until the invoice arrives.<\/li>\n\n<li>Heavy commitment purchases against workloads you&#8217;re about to re-architect are a constraint on your design, and need to be on the table before you propose anything.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If the client wants ongoing cost visibility past what Cost Explorer offers, platforms like Vantage or CloudZero sit on the Cost and Usage Report and give per-team allocation and unit economics that native tooling makes awkward. Raise it as a separate line, not part of the engineering work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Pass test: you can explain the top three cost lines in a sentence each, and account for every month-on-month jump.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pass 6: What happens at two in the morning<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Short pass, about escalation. What support plan is the account on, and would anything wake a human up?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Support plan matters more than people expect. On Basic you get the service limits checks and a small subset of Trusted Advisor security and fault tolerance checks, manual refresh only, and no technical support cases at all. The full check library and the Trusted Advisor API need a paid plan. If your engagement depends on opening a case during a cutover, that&#8217;s a prerequisite, not a nice to have.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s one date worth carrying in your head, because it&#8217;s a real deprecation rather than a version number. AWS has announced that Developer Support, Business Support and Enterprise On-Ramp are discontinued on 1 January 2027, with Business Support+ as the replacement tier. If you&#8217;re scoping anything that runs past that boundary, check which plan the client is on and whether their support cost is about to move.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then monitoring. CloudWatch alarms wired to an SNS topic with no confirmed subscribers are the same failure as the dead trail: configured, visible, useless. If they run anything beyond alarms, whether that&#8217;s Grafana Cloud, Datadog or a self-hosted Prometheus stack, find out who holds the credentials and whether the routing has been tested this year.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Pass test: you can name the human who gets woken up, and the path that reaches them.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The findings that actually move the number<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Unattributable credentials.<\/strong> Each one has to be rotated or removed, and each needs a conversation with whoever might break. That&#8217;s coordination time, which is the hardest kind to compress.<\/li>\n\n<li><strong>No working audit trail.<\/strong> No baseline means you can&#8217;t prove what you did or didn&#8217;t change. Add the cost of standing logging up before the real work starts.<\/li>\n\n<li><strong>Significant drift between code and reality.<\/strong> Reconciliation is slow, fiddly and carries destruction risk. The single largest multiplier I see.<\/li>\n\n<li><strong>No tags.<\/strong> A tax on every subsequent task rather than a task of its own, since every scoping decision becomes an investigation.<\/li>\n\n<li><strong>Resources in unmentioned regions.<\/strong> Each is a small parallel environment with its own security groups, its own logging gap and its own bill.<\/li>\n\n<li><strong>One shared admin identity.<\/strong> Nothing in CloudTrail is attributable to a person, so your first task is identity work the client didn&#8217;t ask for.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">I don&#8217;t present these as criticism. I present them as line items with an hour estimate against each, and let the client decide what&#8217;s in scope. That conversation is much easier before a contract exists than after.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">If you only run five checks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sometimes you get twenty minutes and a screen share.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Pull the credential report and count active keys that haven&#8217;t been used recently.<\/li>\n\n<li>Confirm a CloudTrail trail exists, is multi-region, and is currently delivering.<\/li>\n\n<li>Loop the enabled regions for running instances and NAT gateways.<\/li>\n\n<li>Run a refresh-only plan against whatever infrastructure code exists.<\/li>\n\n<li>Pull six months of cost grouped by service and look at the shape.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Those cover identity, evidence, blast radius, delivery speed and money. The rest of the check refines a picture these five already gave you.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Where this check goes wrong<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Treating it as an audit.<\/strong> You&#8217;re scoping work, not issuing a compliance report. A twenty page findings document makes a prospect defensive and costs you a day.<\/li>\n\n<li><strong>Checking one region.<\/strong> The default region in your profile is not where the problems are.<\/li>\n\n<li><strong>Trusting the console over the API.<\/strong> Configured and working are different states, and the console is better at showing the first.<\/li>\n\n<li><strong>Taking long-lived keys because it&#8217;s faster.<\/strong> Accept an admin access key by email to save fifteen minutes and you&#8217;ve become part of the problem you were hired to assess.<\/li>\n\n<li><strong>Running the loops unannounced.<\/strong> A burst of API calls from an unfamiliar principal is exactly what GuardDuty exists to flag. Tell them you&#8217;re doing it and roughly when.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently asked questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">How long should an AWS account health check take?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Two to three hours for a single account once you have access, including the write-up. Multi-account organizations take longer but not linearly, because the interesting variation concentrates in one or two accounts and the rest are copies of a landing zone pattern.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should I charge for the assessment?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For a short engagement, folding it into the proposal is fine. For anything substantial, or where the client is shopping several consultants, quote it as a small standalone piece with a written deliverable. It qualifies the client, protects your time, and the findings have value to them even if they hire someone else.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What if the client won&#8217;t give me read-only access before signing?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Quote time and materials, or quote fixed price with an explicit assumptions list naming every unknown. Do not quote a fixed price on an account you haven&#8217;t seen. The pattern that works is a small paid discovery phase, then a fixed price set once you know what you&#8217;re dealing with.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Is ReadOnlyAccess enough on its own?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s broader than you probably want. <code>ReadOnlyAccess<\/code> extends to reading data in some services, not just configuration, which is more than you need for scoping and more than many clients will be comfortable granting. The <code>SecurityAudit<\/code> and <code>ViewOnlyAccess<\/code> pairing is narrower and easier to justify in writing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I automate the whole thing?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The enumeration, yes, and it&#8217;s worth scripting since you&#8217;ll run it repeatedly. The interpretation, no. Whether five unused IAM users are a problem depends entirely on what the client is trying to do, and that judgement is most of what you&#8217;re selling.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What if I find something actively compromised?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Stop and tell them immediately, in writing, with timestamps. Don&#8217;t remediate unilaterally: you&#8217;re read-only, and containment steps can destroy evidence. Your job is a clear description of what you saw and when, so they can involve whoever they need to.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does this apply to small accounts too?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Especially to small accounts. Larger organizations usually have some governance, even imperfect. A three year old account at a small business that four different contractors have touched is where the surprises live, and it&#8217;s exactly the account that gets described as simple.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The one thing worth remembering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An AWS account health check isn&#8217;t a security exercise dressed up as sales. It&#8217;s the only reliable way to price uncertainty, and uncertainty is what you&#8217;re actually quoting on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you take one habit from this, take the second pass. Everything else on the list is something you&#8217;d eventually trip over while doing the work. A logging gap is different, because it stays invisible while it costs you, and by the time it matters you&#8217;re the person who was in the account when it happened. Check the trail is delivering. Then check the rest.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Need a second pair of eyes on an AWS account?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I do this for consultancies scoping engagements and for businesses who inherited an account they don&#8217;t fully understand. Usually that looks like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A read-only AWS account health check with a written findings list, hour estimates per item, and a clear split between urgent and later.<\/li>\n\n<li>Cross-account audit role setup, with a trust policy and external ID you can hand to any consultant without granting more than you meant to.<\/li>\n\n<li>Credential archaeology: working out what every active access key belongs to, then rotating or removing the ones that belong to nothing.<\/li>\n\n<li>Getting CloudTrail, AWS Config and log retention into a state where they&#8217;re delivering rather than just configured.<\/li>\n\n<li>Terraform drift reconciliation, including importing unmanaged resources back under code without a destructive plan.<\/li>\n\n<li>Cost shape reviews: the forgotten NAT gateways, orphaned volumes and idle regions, plus budgets and anomaly alerts that reach a real inbox.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;ve got a credential report, a <code>terraform plan<\/code> output or a Cost Explorer screenshot you can&#8217;t make sense of, send it over and I&#8217;ll tell you what I&#8217;d look at next.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/www.upwork.com\/freelancers\/~01f15a912ad84a6620\" target=\"_blank\" rel=\"noreferrer noopener\">Work with me on Upwork<\/a><\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Before I quote AWS work, I run a read-only account health check: six passes covering identity, logging, region sprawl, infrastructure code, cost shape and support escalation. Here are the commands, the pass tests, and the findings that change the number.<\/p>\n","protected":false},"author":1,"featured_media":553,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[25,612,674],"tags":[93,821,103,480,818,426,833,477,705,615,298,727,99,627,613,834,483,88,832],"class_list":["post-552","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cloud-computing","category-cloud-security","category-finops","tag-aws","tag-aws-regions","tag-cloud-security","tag-cloudtrail","tag-confused-deputy","tag-cost-explorer","tag-credential-report","tag-cross-account-access","tag-drift-detection","tag-external-id","tag-freelancing","tag-guardduty","tag-iam","tag-iam-access-analyzer","tag-least-privilege","tag-project-scoping","tag-root-access-management","tag-terraform","tag-trusted-advisor","entry","has-media"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>AWS Account Health Check Before You Quote a Project<\/title>\n<meta name=\"description\" content=\"The AWS account health check I run before quoting a project: six read-only passes that show what an account really costs before you name a price.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"AWS Account Health Check Before You Quote a Project\" \/>\n<meta property=\"og:description\" content=\"The AWS account health check I run before quoting a project: six read-only passes that show what an account really costs before you name a price.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/\" \/>\n<meta property=\"og:site_name\" content=\"John Nessime\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-24T09:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-14T13:13:09+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/aws-account-health-check-waterline.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"627\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"John Nessime\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"John Nessime\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/\"},\"author\":{\"name\":\"John Nessime\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\"},\"headline\":\"The AWS Account Health Check I Run Before Quoting a Project\",\"datePublished\":\"2026-08-24T09:00:00+00:00\",\"dateModified\":\"2026-09-14T13:13:09+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/\"},\"wordCount\":3288,\"publisher\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\"},\"image\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/aws-account-health-check-waterline.png\",\"keywords\":[\"AWS\",\"AWS Regions\",\"Cloud Security\",\"CloudTrail\",\"Confused Deputy\",\"Cost Explorer\",\"Credential Report\",\"Cross-Account Access\",\"Drift Detection\",\"External ID\",\"Freelancing\",\"GuardDuty\",\"IAM\",\"IAM Access Analyzer\",\"Least Privilege\",\"Project Scoping\",\"Root Access Management\",\"Terraform\",\"Trusted Advisor\"],\"articleSection\":[\"Cloud Computing\",\"Cloud Security\",\"FinOps\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/\",\"name\":\"AWS Account Health Check Before You Quote a Project\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/aws-account-health-check-waterline.png\",\"datePublished\":\"2026-08-24T09:00:00+00:00\",\"dateModified\":\"2026-09-14T13:13:09+00:00\",\"description\":\"The AWS account health check I run before quoting a project: six read-only passes that show what an account really costs before you name a price.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/#primaryimage\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/aws-account-health-check-waterline.png\",\"contentUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/aws-account-health-check-waterline.png\",\"width\":1200,\"height\":627,\"caption\":\"Diagram of an AWS account health check: a short teal bar above a dashed waterline labelled what the brief describes, and four longer red and amber bars below it for unattributable access keys, CloudTrail not delivering, Terraform drift and untagged extra regions, beside a numbered list of the six read-only passes.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/cloud-computing\\\/aws-account-health-check\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"The AWS Account Health Check I Run Before Quoting a Project\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/\",\"name\":\"John Nessime\",\"description\":\"Cloud, DevOps, Data &amp; AI \u2014 Built, Tested, Explained\",\"publisher\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\",\"name\":\"John Nessime\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\",\"contentUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\",\"width\":512,\"height\":512,\"caption\":\"John Nessime\"},\"logo\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\"},\"description\":\"AWS Certified Solutions Architect helping businesses build reliable cloud, data, reporting, and automation solutions. I help startups, agencies, and growing businesses replace manual processes and disconnected data with practical AWS architectures, clean data pipelines, useful dashboards, and maintainable automation.\",\"sameAs\":[\"https:\\\/\\\/john-nessime.com\\\/blog\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/john-m-nessime\"],\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/author\\\/johnnessime\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"AWS Account Health Check Before You Quote a Project","description":"The AWS account health check I run before quoting a project: six read-only passes that show what an account really costs before you name a price.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/","og_locale":"en_US","og_type":"article","og_title":"AWS Account Health Check Before You Quote a Project","og_description":"The AWS account health check I run before quoting a project: six read-only passes that show what an account really costs before you name a price.","og_url":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/","og_site_name":"John Nessime","article_published_time":"2026-08-24T09:00:00+00:00","article_modified_time":"2026-09-14T13:13:09+00:00","og_image":[{"width":1200,"height":627,"url":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/aws-account-health-check-waterline.png","type":"image\/png"}],"author":"John Nessime","twitter_card":"summary_large_image","twitter_misc":{"Written by":"John Nessime","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/#article","isPartOf":{"@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/"},"author":{"name":"John Nessime","@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105"},"headline":"The AWS Account Health Check I Run Before Quoting a Project","datePublished":"2026-08-24T09:00:00+00:00","dateModified":"2026-09-14T13:13:09+00:00","mainEntityOfPage":{"@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/"},"wordCount":3288,"publisher":{"@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105"},"image":{"@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/#primaryimage"},"thumbnailUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/aws-account-health-check-waterline.png","keywords":["AWS","AWS Regions","Cloud Security","CloudTrail","Confused Deputy","Cost Explorer","Credential Report","Cross-Account Access","Drift Detection","External ID","Freelancing","GuardDuty","IAM","IAM Access Analyzer","Least Privilege","Project Scoping","Root Access Management","Terraform","Trusted Advisor"],"articleSection":["Cloud Computing","Cloud Security","FinOps"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/","url":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/","name":"AWS Account Health Check Before You Quote a Project","isPartOf":{"@id":"https:\/\/john-nessime.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/#primaryimage"},"image":{"@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/#primaryimage"},"thumbnailUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/aws-account-health-check-waterline.png","datePublished":"2026-08-24T09:00:00+00:00","dateModified":"2026-09-14T13:13:09+00:00","description":"The AWS account health check I run before quoting a project: six read-only passes that show what an account really costs before you name a price.","breadcrumb":{"@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/#primaryimage","url":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/aws-account-health-check-waterline.png","contentUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/aws-account-health-check-waterline.png","width":1200,"height":627,"caption":"Diagram of an AWS account health check: a short teal bar above a dashed waterline labelled what the brief describes, and four longer red and amber bars below it for unattributable access keys, CloudTrail not delivering, Terraform drift and untagged extra regions, beside a numbered list of the six read-only passes."},{"@type":"BreadcrumbList","@id":"https:\/\/john-nessime.com\/blog\/cloud-computing\/aws-account-health-check\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/john-nessime.com\/blog\/"},{"@type":"ListItem","position":2,"name":"The AWS Account Health Check I Run Before Quoting a Project"}]},{"@type":"WebSite","@id":"https:\/\/john-nessime.com\/blog\/#website","url":"https:\/\/john-nessime.com\/blog\/","name":"John Nessime","description":"Cloud, DevOps, Data &amp; AI \u2014 Built, Tested, Explained","publisher":{"@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/john-nessime.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105","name":"John Nessime","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png","url":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png","contentUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png","width":512,"height":512,"caption":"John Nessime"},"logo":{"@id":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png"},"description":"AWS Certified Solutions Architect helping businesses build reliable cloud, data, reporting, and automation solutions. I help startups, agencies, and growing businesses replace manual processes and disconnected data with practical AWS architectures, clean data pipelines, useful dashboards, and maintainable automation.","sameAs":["https:\/\/john-nessime.com\/blog","https:\/\/www.linkedin.com\/in\/john-m-nessime"],"url":"https:\/\/john-nessime.com\/blog\/author\/johnnessime\/"}]}},"_links":{"self":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts\/552","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/comments?post=552"}],"version-history":[{"count":1,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts\/552\/revisions"}],"predecessor-version":[{"id":557,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts\/552\/revisions\/557"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/media\/553"}],"wp:attachment":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/media?parent=552"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/categories?post=552"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/tags?post=552"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}