Raxx · internal docs

internal · gated

AWS KMS cost attribution + model

Date: 2026-07-24 Author: sre-agent Type: Investigation + monitoring + cost-model deliverable (not an incident — no RCA format used) Account: 521228113048 · Regions checked: us-east-1, us-east-2, us-west-1, us-west-2 Related: docs/ops/runbooks/aws-kms.md · Grafana dashboard kms-cost-tracking (terraform/ci-monitoring/)


1. Attribution — what's actually driving the bill

1.1 Headline finding

The current KMS dollar cost is driven entirely by a different product ("Avenue"), not by Raxx/TradeMasterAPI, and not by request volume.

AWS account 521228113048 is shared between Raxx/TradeMasterAPI (us-east-2 workloads: Lightsail vault host, SSM, RDS, Lambda, EBS, ACM — all using AWS-managed default keys, alias/aws/*, which are free) and a separate product called "Avenue" (dev + staging environments, us-east-1 + a DR replica in us-west-2), which owns 11 customer-managed keys (CMKs) plus 1 DR replica:

Alias Purpose (per key description) Region
avenue-dev-rds, avenue-staging-rds RDS storage encryption us-east-1
avenue-dev-secrets, avenue-staging-secrets Secrets Manager encryption us-east-1
avenue-dev-media, avenue-staging-media Avatar/media S3 object-store encryption us-east-1
avenue-dev-feedback, avenue-staging-feedback Beta-feedback S3 object-store encryption us-east-1
avenue-dev-logs, avenue-staging-logs CloudWatch Logs encryption (also used as the CloudTrail trail KMS key — see §1.3) us-east-1
avenue-tf-state Terraform state encryption (primary) us-east-1
avenue-tf-state (DR replica) Terraform state encryption (DR) us-west-2

Each CMK costs a flat $1/month regardless of usage. 11 CMKs in us-east-1 + 1 in us-west-2 = ~$11.68/month in flat key fees (Cost Explorer shows $9.71 for us-east-1 + $0.97 for us-west-2 over the trailing 30 days, prorated for keys created mid-period — total $10.68/month).

Cost Explorer, grouped by USAGE_TYPE for SERVICE = AWS Key Management Service, trailing 30 days:

Usage type Cost Quantity
us-east-1-KMS-Keys $9.71 9.7 key-months
us-west-2-KMS-Keys $0.97 1.0 key-months
us-east-1-KMS-Requests $0.00 14,409 requests
us-east-2-KMS-Requests $0.00 6,457 requests
us-west-2-KMS-Requests $0.00 6 requests
Total $10.68

Request-volume cost is zero — both regions' request counts are within the 20,000/month account-wide free tier. 100% of the current KMS bill is the flat per-CMK fee on "Avenue" keys. Raxx's own KMS footprint (us-east-2) uses only free AWS-managed keys and negligible request volume.

1.2 The one Raxx-specific approved use case is not live yet

The only Raxx-specific, operator-approved KMS use case is the audit-chain HMAC (~$2/mo approved, KMS.GenerateMac/VerifyMac, implemented in backend_v2/api/services/customer_audit_writer_service.py). Checked directly:

Conclusion: the approved ~$2/mo audit-chain baseline is contributing $0 today because the feature has not been deployed to a live KMS key yet. This is worth flagging on its own — when it does go live, watch it against this baseline (dashboard panel included, currently at zero — see §2).

1.3 Why request-volume (CloudTrail) counts are so high, and who's calling

CloudTrail attribution (avenue-trail, a multi-region trail that captures every region's KMS calls, including Raxx's us-east-2 activity) over a 14-day window (2026-07-10 → 2026-07-24):

eventName Count (14d) Top invoker Count from that invoker
GenerateDataKey 232,748 fas.s3.amazonaws.com 186,165
Decrypt 92,079 fas.s3.amazonaws.com 80,271
CreateGrant 1,856 autoscaling.amazonaws.com 1,856
RetireGrant 1,853 (paired with CreateGrant)
GenerateDataKeyWithoutPlaintext 929 autoscaling.amazonaws.com 929
Encrypt 248 mixed

Digging into the fas.s3.amazonaws.com-invoked GenerateDataKey calls by target key ARN:

Both of these CMKs are the exact keys CloudTrail uses to SSE-KMS-encrypt each trail's own delivered log files in S3 (avenue-trail's KmsKeyId = avenue-dev-logs; avenue-staging-trail's KmsKeyId = avenue-staging-logs, per aws cloudtrail describe-trails). CloudTrail is, in large part, encrypting its own output and that shows up as the single biggest KMS caller in the account. The second-largest attributed invoker, logs.amazonaws.com (Decrypt 25,159 + GenerateDataKey 18,386 = 43,545 over 14 days), is CloudWatch Logs group encryption on the same two CMKs — again Avenue-side infrastructure, not Raxx.

Anomaly flagged, not acted on: the account has two separate multi-region CloudTrail trails (avenue-trail and avenue-staging-trail), both owned by Avenue, both capturing the identical account-wide event stream (verified: identical per-eventName counts in both trails' log groups for the same window). Running two full multi-region trails when one would capture the same data is redundant — it roughly doubles CloudTrail's own S3-delivery/KMS overhead. This is an Avenue-side infrastructure decision, out of Raxx/sre-agent's scope to change, but is surfaced here for the operator's account-wide cost awareness, since it's the single largest contributor to raw KMS call volume in the account (though, per §1.1, not to current cost, since that volume is still inside the free tier).

CreateGrant/RetireGrant/GenerateDataKeyWithoutPlaintext from autoscaling.amazonaws.com in us-east-2 (Raxx's region) correlate with the Woodpecker CI spot-runner ASG cycling encrypted EBS boot volumes on instance launch/terminate (see docs/incidents/2026-07-13-ci-spot-wedge-silent-outage.md). This IS Raxx-adjacent infrastructure, but grant operations aren't currently billed per-request, so the dollar impact is $0; it's included in the dashboard purely as an operational-churn signal.

1.4 Volume trend (2026-07-10 → 2026-07-24, GenerateDataKey + Decrypt + Encrypt + GenerateDataKeyWithoutPlaintext, daily)

07-10:  5,975   07-15: 19,491   07-20: 29,889
07-11: 14,932   07-16: 21,058   07-21: 30,498
07-12: 15,171   07-17: 29,605   07-22: 27,669
07-13: 15,477   07-18: 28,943   07-23: 23,421
07-14: 15,450   07-19: 28,932   07-24: 19,493 (partial day)

Raw daily call volume roughly 5x'd over the two-week window (day 1 partial ~6k → peak ~30.5k on 07-21), then pulled back slightly in the last 2 days. This is a genuine growth trend worth watching even though it isn't costing money today — it consumed most of the account's request free tier by mid-window on a naive daily-rate basis.

1.5 Open discrepancy — flagged explicitly, not resolved

CloudTrail shows ~330,000 crypto operations over 14 days (§1.3). Cost Explorer's billed *-KMS-Requests quantity for the trailing 30 days is only ~20,872 total across all regions, and its cost is $0. These numbers don't reconcile, and this document does not paper over that. Leading (unverified) hypotheses:

  1. S3 Bucket Keys may be enabled on the CloudTrail delivery buckets and/or Avenue's S3 buckets, which would mean most individual object operations reuse a cached data key (no new billed KMS request) even though CloudTrail may still log a management-event record per underlying S3 request in some configurations. Not yet confirmed either way.
  2. Billing-metric lag/aggregation: Cost Explorer daily data can lag 24-48h and aggregates differently than CloudTrail's per-event stream; a systematic under-count this large (15-20x) would be unusual but hasn't been ruled out.
  3. Free-operation misclassification: some fraction of the counted events may not be separately-billed "requests" under AWS's current KMS pricing model (only cryptographic operations are billed; some administrative/list operations are not — but GenerateDataKey/Decrypt are unambiguously billable operations per AWS's own pricing page, so this alone doesn't fully explain the gap).

What resolves this going forward: the live dashboard (§2, Panel 0) tracks the actual billed dollar figure directly from AWS/Billing, so the operator doesn't need this discrepancy resolved to trust the cost number — it's authoritative regardless. The CloudTrail-volume panels (once enabled, see §2) will let us watch whether raw volume growth ever translates into billed cost, which would itself be informative data toward resolving the discrepancy.


2. Tracking now live

Chosen approach: native CloudWatch dashboard (Grafana, on the existing terraform/ci-monitoring/ stack), using two data sources:

  1. Live today, zero new AWS resources: the native AWS/Billing EstimatedCharges metric (ServiceName=awskms), which this account already emits (billing alerts were already enabled — confirmed via aws cloudwatch list-metrics --namespace AWS/Billing). This is the authoritative $ figure and requires no dependency on any other team's infrastructure.
  2. Proposed, gated off pending operator/Avenue-team sign-off: CloudWatch Logs metric filters on avenue-trail (the account's only source of account-wide KMS request-volume data) emitting 6 custom metrics (kms_requests_total, kms_generate_data_key, kms_decrypt, kms_generate_mac, kms_verify_mac, kms_create_grant) into namespace MooseQuest/KMS. Code is written and reviewed-ready in terraform/ci-monitoring/kms-cost-tracking.tf, gated behind var.enable_avenue_trail_kms_metrics (default false).

Why gated, not applied

Attaching a CloudWatch Logs metric filter to avenue-trail is read-only and does not modify the trail's own behavior, retention, or cost — but it does attach a Raxx-owned monitoring resource to another product's shared infrastructure, which is a cross-team resource decision, not a routine "extend our own stack" action. sre-agent did not apply this without sign-off. The IAM policy needed for Grafana to read any CloudWatch metric (ci-monitoring-cloudwatch-read, pre-existing in terraform/ci-monitoring/iam.tf but not yet applied — this was found as pre-existing drift, unrelated to this task, and applied as part of this work since it's Raxx's own IAM role) has been applied, so Panel 0 is fully live.

Why not a brand-new Raxx-owned CloudTrail trail instead? Considered and rejected for now: the account already has two trails providing full multi-region coverage; adding a third (even management-events-only) would be a genuinely new recurring cost (additional-trail pricing, ~$2/100k events, plus S3 storage) and a new spend decision, which is explicitly out of scope for this deliverable ("no unilateral spend increase... surface as an operator decision"). If the operator wants Raxx to own its own trail independent of Avenue, that's a clean option but needs an explicit go-ahead given the cost.

Where to view it

Grafana dashboard AWS KMS Request Volume & Cost Attribution (uid: kms-cost-tracking) on the ci-monitoring host:

aws ssm start-session --region us-east-2 --target <ci-monitoring-instance-id> \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["3000"],"localPortNumber":["3000"]}'
# then http://localhost:3000 -> AWS KMS Request Volume & Cost Attribution

Note on deployment: the dashboard JSON is delivered via the cloud-init template in this PR. Because the ci-monitoring EC2 instance's user_data is set to ignore_changes (to avoid instance replacement on every template edit — see main.tf), the new dashboard file needs one manual push to the live instance after merge, per the existing "Updating configs" procedure in docs/ops/runbooks/ci-monitoring.md (place the JSON in /opt/monitoring/grafana/dashboards/; Grafana's provisioning watcher picks it up within 30s). sre-agent did not perform this push directly — that action goes through the reviewed commit/PR path per the operator's own instruction, not an ad hoc live-host edit.

Cost of what's live: $0 (the billing metric is free; the one IAM policy applied has no cost). Cost of what's proposed-but-gated: ~$1.80/month if enabled (6 custom metrics × ~$0.30/month each).


3. Cost model

3.1 Pricing used (confirmed current, 2026)

3.2 Current monthly run-rate, by driver

Driver Monthly cost Basis
Avenue CMKs (11 in us-east-1 + 1 DR in us-west-2) ~$10.68 - $12.00 Flat $1/mo/CMK; Cost Explorer trailing-30d actual = $10.68
Raxx us-east-2 AWS-managed keys (6 keys) $0.00 AWS-managed keys have no monthly fee
Request volume, all regions $0.00 ~20,872 requests/month (Cost Explorer), inside the 20,000 free tier (essentially at the boundary — see §1.5 re: raw CloudTrail volume being much higher and unreconciled)
Audit-chain HMAC (Raxx, approved) $0.00 today (~$2.00/mo when live) Not deployed — no HMAC key exists, zero GenerateMac/VerifyMac events
Total current run-rate ~$10.68/month 100% flat per-CMK fees on Avenue's keys

3.3 How each driver scales

3.4 Scaling projection (illustrative, from current measured volumes)

Scenario Avenue CMK cost Request cost (if it started being billed at current CloudTrail-observed rate) Audit-chain HMAC (if live)
Current (1x) ~$10.68-12/mo $0 (within free tier per CE) $0 (not deployed)
10x (e.g., Avenue adds prod env + 10x traffic) ~$20-25/mo (if CMK count roughly doubles for a prod tier) ~330k/day-equivalent × 10 ≈ 3.3M crypto ops/mo → (3.3M − 20k)/10,000 × $0.03 ≈ ~$9.84/mo if this volume were ever billed ~$20/mo (linear with audit events)
100x (hypothetical, stress case) Key count doesn't scale 100x meaningfully (keys are provisioned, not usage-driven) — assume same ~$20-25/mo ~33M crypto ops/mo → ~$99/mo if billed at the CloudTrail-observed rate ~$200/mo (linear with audit events, before considering the quarterly full-history re-verify getting proportionally larger too)

Caveat: the 10x/100x request-cost columns use the unreconciled CloudTrail volume (§1.5), not the currently-billed Cost Explorer volume, as the base rate — deliberately conservative/pessimistic, since we don't yet know why they disagree. If the discrepancy resolves toward the lower Cost Explorer number, these projections would be ~15-20x too high (worst case, not best guess).

3.5 Quick wins (informational — none require sre-agent spend authority beyond what's already applied)

  1. Consolidate the two Avenue multi-region CloudTrail trails into one. This is the single largest lever on raw KMS call volume (halves the CloudTrail-self-inflicted GenerateDataKey/Decrypt overhead). Out of scope for Raxx/sre-agent — this is Avenue-owned infrastructure; flagged here for the operator's account-wide awareness only.
  2. Enable S3 Bucket Keys on any Avenue S3 buckets using SSE-KMS with a CMK, if not already enabled — dramatically reduces GenerateDataKey call volume for high-frequency object writes by caching data keys at the bucket level (AWS default TTL ~5 min). Same ownership caveat as above.
  3. Data-key caching in the Raxx audit-chain HMAC service, when it goes live: compute_event_hash already does one GenerateMac call per write with no batching — this is appropriate for HMAC (there's no data-key caching pattern for GenerateMac/VerifyMac, unlike Encrypt/Decrypt+GenerateDataKey), so no change needed there. The one real lever is bounding the nightly window (already done, 24h) and being deliberate about quarterly full-history re-verify cost as the audit log grows — consider whether quarterly cadence should become less frequent, or windowed/incremental with periodic checkpoints, once audit-event volume is large enough that quarterly full-history verification becomes materially expensive. Not urgent today (feature isn't live).
  4. The audit-chain HMAC's approved ~$2/mo baseline should be revisited once deployed to include the quarterly full-history re-verify cost, which the original estimate likely didn't account for (see §3.3).

None of the above were acted on unilaterally — items 1-2 are outside Raxx's ownership boundary, and items 3-4 are forward-looking notes for when a not-yet-deployed feature ships, not current-state changes.


4. Summary for the operator