@@ -36,12 +36,13 @@ For detailed API endpoints and request schemas, see [references/api-reference.md
3636- Update a user's roles (org-level or project-level)
3737- Remove a user from the organization
3838- Create an additional Cloud API key with scoped roles and expiration
39+ - Create a Cloud API key that can also call Elasticsearch and Kibana APIs on Serverless projects
3940- List and revoke Cloud API keys
4041- Create a custom role inside a Serverless project with ES cluster, index, and Kibana privileges
4142- Assign or remove a custom role for a user on a Serverless project using the Cloud API's ` application_roles `
4243- Translate a natural-language access request into invite, role, and API key tasks
4344
44- ## Prerequisites
45+ ## Prerequisites and permissions
4546
4647| Item | Description |
4748| -------------------- | ------------------------------------------------------------------------------------------------------- |
@@ -54,6 +55,62 @@ For detailed API endpoints and request schemas, see [references/api-reference.md
5455Run ` python3 skills/cloud/access-management/scripts/cloud_access.py list-members ` to verify that ` EC_API_KEY ` is valid
5556and auto-discover the org ID before proceeding with any operation.
5657
58+ ### Operation-level permissions
59+
60+ The following permissions are required for common access management operations in Elastic Cloud Serverless.
61+
62+ | Operation | Required permission |
63+ | ---------------------------------- | -------------------------------------------------------------- |
64+ | Invite / remove members | Organization owner (` organization-admin ` ) |
65+ | Assign or remove roles | Organization owner (` organization-admin ` ) |
66+ | Create / revoke Cloud API keys | Organization owner (` organization-admin ` ) |
67+ | List members, invitations, or keys | Any organization member |
68+ | Create / delete custom roles | ` manage_security ` cluster privilege on the project ES endpoint |
69+
70+ This skill does not perform a separate role pre-check. Attempt the requested operation and let the API enforce
71+ authorization. If the API returns an authorization error (for example, ` 403 Forbidden ` ), stop and ask the user to verify
72+ the provided API key permissions.
73+
74+ ### Manual setup fallback (when cloud-setup is unavailable)
75+
76+ If this skill is installed standalone and ` cloud-setup ` is not available, instruct the user to configure Cloud
77+ environment variables manually before running commands. Never ask the user to paste API keys in chat.
78+
79+ | Variable | Required | Description |
80+ | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
81+ | ` EC_API_KEY ` | Yes | Elastic Cloud API key with Organization owner role. |
82+ | ` EC_BASE_URL ` | No | Cloud API base URL (default: ` https://api.elastic-cloud.com ` ). |
83+ | ` ELASTICSEARCH_URL ` | Conditional | Elasticsearch URL. Required only for custom role operations. |
84+ | ` ELASTICSEARCH_API_KEY ` | Conditional | Elasticsearch API key with ` manage_security ` privilege. Required only for custom role operations. |
85+
86+ > ** Note:** If ` EC_API_KEY ` is missing, or the user does not have a Cloud API key yet, direct the user to generate one
87+ > at [ Elastic Cloud API keys] ( https://cloud.elastic.co/account/keys ) , then configure it locally using the steps below.
88+
89+ Preferred method (agent-friendly): create a ` .env ` file in the project root:
90+
91+ ``` bash
92+ EC_API_KEY=your-api-key
93+ EC_BASE_URL=https://api.elastic-cloud.com
94+ # Only needed for custom role operations against the project Elasticsearch endpoint:
95+ # ELASTICSEARCH_URL=https://<project-id>.es.<region>.elastic-cloud.com
96+ # ELASTICSEARCH_API_KEY=<your-es-manage-security-api-key>
97+ ```
98+
99+ All ` cloud/* ` scripts auto-load ` .env ` from the working directory.
100+
101+ Alternative: export directly in the terminal:
102+
103+ ``` bash
104+ export EC_API_KEY=" <your-cloud-api-key>"
105+ export EC_BASE_URL=" https://api.elastic-cloud.com"
106+ # Only needed for custom role operations against the project Elasticsearch endpoint:
107+ # export ELASTICSEARCH_URL="https://<project-id>.es.<region>.elastic-cloud.com"
108+ # export ELASTICSEARCH_API_KEY="<your-es-manage-security-api-key>"
109+ ```
110+
111+ Terminal exports may not be visible to sandboxed agents running in separate shell sessions, so prefer ` .env ` when using
112+ an agent.
113+
57114## Decomposing Access Requests
58115
59116When the user describes access in natural language (for example, "add Alice to my search project as a developer"), break
@@ -150,8 +207,8 @@ Elasticsearch security API and assign it to users through the Cloud API's `appli
150207 ` observability-viewer ` , or ` security-viewer ` ) and sets ` application_roles ` to the custom role name. This ensures the
151208 user can see and access the project in the Cloud console but receives only the custom role's restricted permissions
152209 inside the project.
153- - Cloud API keys ( ` create-api-key ` ) currently carry Cloud roles only. Support for assigning custom roles to Cloud API
154- keys is planned and will be documented here once available in production .
210+ - Cloud API keys can also use ` application_roles ` to gain ES/Kibana API access on Serverless projects. See
211+ [ Cloud API Keys — ES and Kibana API Access ] ( #cloud-api-keys--es-and-kibana-api-access ) below for details .
155212
156213### Canonical custom-role onboarding flow
157214
@@ -187,6 +244,88 @@ not available in Serverless.
187244
188245For advanced DLS/FLS patterns (templated queries, ABAC), see the ** elasticsearch-authz** skill.
189246
247+ ## Cloud API Keys — ES and Kibana API Access
248+
249+ Cloud API keys can now optionally access Elasticsearch and Kibana APIs on Serverless projects, in addition to the Cloud
250+ API. This enables a single credential for both control plane (Cloud API) and data plane (ES/Kibana API) operations — for
251+ example, a CI pipeline that creates a project via Cloud API and then indexes data via ES API.
252+
253+ ### How it works
254+
255+ Add ` application_roles ` to the key's ` role_assignments ` at creation time. This field accepts an array of predefined role
256+ names (` admin ` , ` developer ` , ` viewer ` , and solution-specific roles like ` t1_analyst ` , ` editor ` ) or custom role names
257+ created in the project via ` PUT /_security/role/{name} ` . Predefined roles are available in every project by default.
258+ Custom roles must be created individually in each project where the key should have access — if a referenced custom role
259+ does not exist in a project, the key silently gets no access there.
260+
261+ ### Critical rule: no implicit inheritance
262+
263+ Unlike users, API keys ** never** inherit stack roles from their ` role_id ` . If ` application_roles ` is omitted or empty,
264+ the key has Cloud API access only. Calling an ES or Kibana endpoint with such a key returns ** 403 Forbidden** . This is
265+ by design for backward compatibility — existing keys without ` application_roles ` continue to work as Cloud-only keys.
266+
267+ ### Scoping modes
268+
269+ - ** Project-scoped** (preferred) — grants access to specific projects or all projects of a given type. Uses the
270+ ` project ` key in ` role_assignments ` with ` application_roles ` on each entry. ** Use this by default** unless the user
271+ explicitly needs cross-project access.
272+
273+ - ** Organization-scoped** — grants access to ** all current and future projects** in the organization. Uses the
274+ ` organization ` key in ` role_assignments ` with ` application_roles ` . ** This is the broadest possible data-plane scope.**
275+ Only use when the key genuinely needs access to every project (for example, platform automation or cross-project
276+ search across the whole org). Always confirm with the user before creating an org-scoped key with ` application_roles ` ,
277+ as it grants ES/Kibana access to projects that may not exist yet.
278+
279+ > ** Custom roles and org-scoped access:** When using a custom role name in ` application_roles ` with organization-scoped
280+ > assignments, the custom role must exist in each project where you want the key to have access. If a project does not
281+ > have that custom role defined (via ` PUT /_security/role/{name} ` ), the key silently gets no access to that project — no
282+ > error is raised. For org-wide access, prefer predefined roles (` admin ` , ` developer ` , ` viewer ` ) which are available in
283+ > every project by default. If you must use custom roles across multiple projects, ensure the role is created in each
284+ > target project first.
285+
286+ > ** Agent guidance:** When a user asks for an API key with ES/Kibana access, default to project-scoped assignments. Only
287+ > suggest organization-scoped ` application_roles ` if the user explicitly needs access across all projects. Confirm the
288+ > intent before proceeding — org-scoped access applies to future projects too. If the user specifies a custom role name
289+ > with org-scoped access, warn them that the role must be defined in each project individually.
290+
291+ ### Examples
292+
293+ ** Project-scoped key with developer ES access** (using ` --stack-access ` convenience flag):
294+
295+ ``` bash
296+ python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
297+ --description " CI pipeline - ES ingest" \
298+ --expiration 30d \
299+ --roles ' {"project":{"elasticsearch":[{"role_id":"developer","organization_id":"$ORG_ID","all":true}]}}' \
300+ --stack-access developer
301+ ```
302+
303+ ** Organization-scoped key with admin ES access** (access to ALL projects — use with caution):
304+
305+ ``` bash
306+ python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
307+ --description " Platform automation" \
308+ --expiration 7d \
309+ --roles ' {"organization":[{"role_id":"organization-admin","organization_id":"$ORG_ID"}]}' \
310+ --stack-access admin
311+ ```
312+
313+ ** Project-scoped key with a custom role** (raw JSON):
314+
315+ ``` bash
316+ python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
317+ --description " Marketing ETL" \
318+ --expiration 14d \
319+ --roles ' {"project":{"elasticsearch":[{"role_id":"elasticsearch-viewer","organization_id":"$ORG_ID","all":false,"project_ids":["$PROJECT_ID"],"application_roles":["marketing-writer"]}]}}'
320+ ```
321+
322+ Replace ` $ORG_ID ` and ` $PROJECT_ID ` with the actual organization and project IDs. Use ` list-members ` to discover the org
323+ ID.
324+
325+ > ** Common mistake:** If your API key gets a 403 when calling an ES or Kibana endpoint, the most likely cause is missing
326+ > ` application_roles ` . Unlike users, API keys must have explicit ` application_roles ` to access the stack — the ` role_id `
327+ > alone is not sufficient.
328+
190329## Examples
191330
192331### Invite a user as a Viewer on a search project
@@ -218,6 +357,23 @@ The actual key value is written to a secure temp file (0600 permissions). The st
218357instead of the raw secret. Tell the user to retrieve the key from that file — it is shown only once. When the CI
219358pipeline no longer needs this key, revoke it using ` delete-api-key ` to avoid unused keys accumulating.
220359
360+ ### Create a CI/CD API key with ES access
361+
362+ ** Prompt:** "Create an API key for our CI pipeline that can index data into our search projects."
363+
364+ ``` bash
365+ python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
366+ --description " CI pipeline - ES ingest" \
367+ --expiration 30d \
368+ --roles ' {"project":{"elasticsearch":[{"role_id":"developer","organization_id":"$ORG_ID","all":true}]}}' \
369+ --stack-access developer
370+ ```
371+
372+ Replace ` $ORG_ID ` with the actual organization ID. The ` --stack-access ` flag injects ` application_roles: ["developer"] `
373+ into the role assignments, granting the key developer-level ES/Kibana API access on all Elasticsearch projects. Without
374+ ` --stack-access ` (or explicit ` application_roles ` in the JSON), the key would only have Cloud API access and receive 403
375+ on ES/Kibana calls.
376+
221377### Create a custom role for marketing data
222378
223379** Prompt:** "Create a role that gives read-only access to marketing-\* indices on my search project."
0 commit comments