cis_extensions.api_key_services — version 1.4
Source: presets
Purpose
CIS 4.0 §1.14 / 5.0 §1.15 — API keys restricted to the APIs an application needs.
Opt-in because an API key that today calls anything stops working the moment this is enforced, unless its API targets are already narrowed. Set the allowed services first, then enforce.
The constraint is a MANAGED one carrying a parameter, not a bare boolean —
verified against a live organisation's OrgPolicy ListConstraints. It blocks
creating an API key bound to a service account unless the key's API targets
are non-empty and limited to allowed_api_key_services, which is what the
control asks for.
Use it
params {
cis_api_key_services = true
// the pack's own defaults — copy only the lines you change
allowed_api_key_services = []
}
use "presets/cis/api-key-services.satz" when cis_api_key_services
cis_api_key_services is the gate: while it is falsy the pack contributes nothing — no resources, no params, no claims. It is declared by CIS_GCP_Foundation_4_0, and the estate's own binding wins.
Needs from outside the pack:
customer_organization_id— declared byestate_core; use one of them alongside, or declare it in the estate.
Params
| param | default |
|---|---|
allowed_api_key_services |
[] |
Questions
What to ask before this pack is configured, and what changing the answer costs.
| asks about | prompt | reversal | blast | recommends |
|---|---|---|---|---|
allowed_api_key_services |
The services an API key bound to a service account may target | edit | high | — |
allowed_api_key_services — The pack cannot know which APIs the customer's applications call. Empty is a legitimate answer — no such key may be created at all — but it has to be the customer's answer: every key whose targets are not in this list stops working the moment the control is enforced.
Notices
What to run once this pack is switched on. satz shows each notice when the pack goes on and until the estate binds its param true; every command that writes to the organisation refuses while one whose severity is error is open.
| param | run | severity | what and why |
|---|---|---|---|
cis |
satz adopt <estate> --execute --import |
error | Google may already hold a policy this pack declares on the organisation, set by an administrator or by default, and an apply that creates a policy that exists stops on 409 POLICY_ALREADY_EXISTS. Run satz adopt once the pack is on, so every live policy is in the state before the apply; with --import it binds this param itself when it has run. |
Contributes
| resource type | labels |
|---|---|
google_org_policy_policy |
iam-managed-disable |
Claims
| framework | control | title | kind | witnesses | interpretation |
|---|---|---|---|---|---|
| cis-gcp 4.0 | 1.14 | API keys restricted to the APIs the application needs | implements | google |
A service-account API key may only be created when its API targets are non-empty and limited to the allowed services, so no key exists that can call every enabled API. |
| cis-gcp 5.0 | 1.15 | API keys restricted to the APIs the application needs | implements | google |
As for 4.0 §1.14 — 5.0 §1.15 (5.0 renumbered §1.13–1.16; its 1.13 is a different control, 'API keys only exist for active services'). |
What the controls ask (this project's paraphrase from the catalog — never the framework's own text):
- API keys restricted to the APIs the application needs (cis-gcp 4.0 §1.14, cis-gcp 5.0 §1.15) — An API key may call only the services the application actually uses, never every enabled API.
Manual duties (a control stays partial until each is attested):
review-services(cis-gcp 4.0 §1.14, cis-gcp 5.0 §1.15) — The allowed-service list is the control: re-read it when an application starts calling a new API, rather than switching the constraint off.
Every resource this pack contributes is a witness of a claim above.
History
| version | date | change |
|---|---|---|
| 1.4 | 2026-09-30 | every claim states its measure without satz: gcloud (the commands that meet the control the way the claim's resources do), gcloud_check (the commands that show whether it is met) and risk (what goes wrong without it); require and report-compliance carry them in each control's measures. Nothing emitted changes |
| 1.3 | 2026-09-20 | the notice's before = apply becomes severity = error: the same refusal, stated once by the pack instead of inside the two commands that read it — every command that writes to the organisation refuses while it is open. Nothing emitted changes, and the param that acknowledges it is unchanged |
| 1.2 | 2026-09-19 | a notice: once the pack is switched on, satz adopt <estate> --execute --import is to run before the apply, because a policy it declares may already be live and creating it stops on 409 POLICY_ALREADY_EXISTS. The estate acknowledges it with cis, which adopt --execute --import binds itself when it has run; transpile --apply and bootstrap refuse while it is open. The param is never emitted, so the plan does not move |
| 1.1 | 2026-09-10 | one question block on the allowed services; the empty default blocks on purpose — it is a legitimate answer, but it has to be the customer's. Nothing emitted changes |
| 1.0 | 2026-09-04 | CIS 4.0 1.14 / 5.0 1.15, opt-in: a managed constraint with an allowedServices parameter, not a bare boolean |
The whole library's history: the changelog in presets/CHANGELOG.md.
Notes
Narrow the API targets of every key in use BEFORE this is enforced: a key that today calls anything stops working the moment it is. The allowed-services param has no default for the same reason — the pack cannot know which services a customer's keys call.