@aaa-sentinel/iac-cost 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +64 -5
  2. package/dist/cli.js +682 -116
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -7,6 +7,10 @@ internet is caught in review instead of after the deploy. With `iac-cost fix`, t
7
7
  your last scan on resources this Terraform manages come back to you as **pull requests**, not as
8
8
  console changes (see [Fixes as pull requests](#fixes-as-pull-requests)).
9
9
 
10
+ > **New in 0.4.0:** the `posture` subcommand prints how the security posture of an account changed
11
+ > over 30, 90 or 365 days, with an optional gate on new findings (see
12
+ > [Posture over time](#posture-over-time)). Nothing changes for `fix` or the existing command.
13
+
10
14
  > **Changed in 0.3.1:** the pull requests of `fix` pass `terraform fmt -check`: an added attribute is
11
15
  > aligned with its group (also around heredocs and after removing a block), as `terraform fmt` does.
12
16
 
@@ -171,19 +175,19 @@ value from the plan: a variable is named (`DB_PASSWORD`), its value is not read.
171
175
 
172
176
  | Cloud | Terraform resources | Rules |
173
177
  | --- | --- | --- |
174
- | AWS | `aws_security_group`, `aws_default_security_group`, `aws_security_group_rule`, `aws_vpc_security_group_ingress_rule` (+ `aws_ec2_managed_prefix_list`) | Admin ports (22/3389) open to the internet, directly or through a prefix list |
178
+ | AWS | `aws_security_group`, `aws_default_security_group`, `aws_security_group_rule`, `aws_vpc_security_group_ingress_rule` (+ `aws_ec2_managed_prefix_list`) | Admin ports (22/3389) open to the internet, directly or through a prefix list; database ports (SQL Server, Oracle, MySQL, PostgreSQL, Redis, Elasticsearch, MongoDB) open to the internet over TCP |
175
179
  | AWS | `aws_s3_bucket` + `aws_s3_bucket_public_access_block`, `aws_s3_account_public_access_block`, `aws_s3_bucket_acl`, `aws_s3_bucket_policy` | Public bucket |
176
- | AWS | `aws_db_instance`, `aws_rds_cluster_instance` (+ `aws_rds_cluster`) | Unencrypted storage |
180
+ | AWS | `aws_db_instance`, `aws_rds_cluster_instance` (+ `aws_rds_cluster`) | Unencrypted storage; publicly accessible |
177
181
  | AWS | `aws_lambda_function` + `aws_lambda_function_url`, `aws_lambda_permission` | Invocable from the internet; secret-looking environment variable names |
178
182
  | AWS | `aws_cloudtrail` | Trail stopped; management events excluded |
179
183
  | AWS | `aws_kms_key` | Key rotation off |
180
184
  | AWS | `aws_vpc`, `aws_default_vpc` + `aws_flow_log` (+ `aws_subnet`) | VPC without flow logs; flow logs that miss part of the traffic |
181
- | Azure | `azurerm_network_security_group`, `azurerm_network_security_rule` | Admin ports open to the internet |
185
+ | Azure | `azurerm_network_security_group`, `azurerm_network_security_rule` | Admin ports open to the internet; database ports open to the internet over TCP |
182
186
  | Azure | `azurerm_storage_account` | Public blobs allowed; weak TLS or HTTP |
183
187
  | Azure | `azurerm_mssql_server` + `azurerm_mssql_firewall_rule` | Public network access open to 0.0.0.0–255.255.255.255; weak TLS |
184
188
  | Azure | `azurerm_role_assignment` | Owner or User Access Administrator at any scope; Contributor at subscription scope (a Contributor at a management group or at `/` is not flagged, neither here nor in the live scan) |
185
- | Google Cloud | `google_compute_firewall` | Admin ports open to the internet |
186
- | Google Cloud | `google_sql_database_instance` | Authorized from 0.0.0.0/0; no CMEK |
189
+ | Google Cloud | `google_compute_firewall` | Admin ports open to the internet; database ports open to the internet over TCP |
190
+ | Google Cloud | `google_sql_database_instance` | Authorized from 0.0.0.0/0; no CMEK; unencrypted connections accepted (`ssl_mode`; not judged while the provider still has to compute it) |
187
191
  | Google Cloud | `google_cloudfunctions_function`, `google_cloudfunctions2_function` | Secret-looking environment variable names |
188
192
  | Google Cloud | `google_project_iam_member`, `_binding`, `_policy`; `google_service_account_key` | Primitive Owner/Editor role; user-managed key |
189
193
 
@@ -543,6 +547,61 @@ pipelines:
543
547
 
544
548
  The commit is created through the Bitbucket API (`POST /src`), so the clone needs no push access.
545
549
 
550
+ ## Posture over time
551
+
552
+ `iac-cost posture` reads `GET /v1/security/posture-history` and prints, for one account and a
553
+ period of 30, 90 or 365 days:
554
+
555
+ - the Security Score from the first to the last day with a scan (the last scan of each day), or
556
+ why it is **not comparable** (the rule catalog or what could be evaluated changed in between);
557
+ - distinct findings **resolved** (gone in a scan that fully evaluated their rule) and **new**
558
+ (from rules that were already fully evaluated before);
559
+ - findings that cannot be compared (their rule was not fully evaluated in the previous scan, or
560
+ they come from an external tool) and findings gone without confirmation;
561
+ - the mean time to fix critical and high findings.
562
+
563
+ ```bash
564
+ export IAC_SECURITY_CLIENT_ID=… # OAuth2 app with the security:read scope
565
+ export IAC_SECURITY_CLIENT_SECRET=…
566
+ npx --yes @aaa-sentinel/iac-cost posture --account acc_01j… --days 90
567
+ ```
568
+
569
+ | Option | Default | |
570
+ | --- | --- | --- |
571
+ | `--account <id>` | `IAC_SECURITY_ACCOUNT_ID` | AAA Sentinel account id |
572
+ | `--days <30\|90\|365>` | `90` | Period |
573
+ | `--format <markdown\|text\|json>` | `markdown` | `json` prints the API response as is |
574
+ | `--fail-on-new <severity>` | — | Exit 1 when new findings at this severity or worse appeared in the period |
575
+
576
+ Exit codes: `1` only when `--fail-on-new` trips; `2` for a usage error (an option without a value,
577
+ a stray argument), missing credentials or, with the gate, a 4xx from the API (wrong scope or
578
+ account); `0` otherwise, also when the API does not answer (a warning explains why). Findings that
579
+ cannot be compared with the previous scan do not count for the gate.
580
+
581
+ A weekly job that writes the summary to the run page:
582
+
583
+ ```yaml
584
+ name: Security posture
585
+ on:
586
+ schedule:
587
+ - cron: '0 7 * * 1'
588
+ workflow_dispatch:
589
+
590
+ jobs:
591
+ posture:
592
+ runs-on: ubuntu-latest
593
+ steps:
594
+ - uses: actions/setup-node@v4
595
+ with:
596
+ node-version: 22
597
+ - name: Posture over the last 90 days
598
+ env:
599
+ IAC_SECURITY_CLIENT_ID: ${{ secrets.IAC_SECURITY_CLIENT_ID }}
600
+ IAC_SECURITY_CLIENT_SECRET: ${{ secrets.IAC_SECURITY_CLIENT_SECRET }}
601
+ run: |
602
+ npx --yes @aaa-sentinel/iac-cost posture --account ${{ vars.AAA_SENTINEL_ACCOUNT_ID }} --days 90 --fail-on-new critical >> "$GITHUB_STEP_SUMMARY"
603
+ ```
604
+
546
605
  ## Why there is no comment
547
606
 
548
607
  | What you see | Why |