cloud-access-management

>

elastic/agent-skills2.6k installsApache-2.0Synced Aug 31

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: cloud-access-management
description: >
license: Apache-2.0
---

# Cloud Access Management

Manage identity and access for an Elastic Cloud organization and its Serverless projects: invite users, assign
predefined or custom roles, and manage Cloud API keys.

> **Prerequisite:** This skill assumes the **cloud-setup** skill has already run — `EC_API_KEY` is set in the
> environment and the organization context is established. If `EC_API_KEY` is missing, instruct the agent to invoke
> **cloud-setup** first. Do NOT prompt the user for an API key directly.

For project creation, see the **cloud-create-project** skill. For day-2 project operations (list, update, delete), see
**cloud-manage-project**. For Elasticsearch-level role management (native users, role mappings, DLS/FLS), see the
**elasticsearch-authz** skill.

For detailed API endpoints and request schemas, see [references/api-reference.md](references/api-reference.md).

## Jobs to Be Done

- Invite a user to the organization and assign them a Serverless project role
- List organization members and their current role assignments
- Update a user's roles (org-level or project-level)
- Remove a user from the organization
- Create an additional Cloud API key with scoped roles and expiration
- Create a Cloud API key that can also call Elasticsearch and Kibana APIs on Serverless projects
- List and revoke Cloud API keys
- Create a custom role inside a Serverless project with ES cluster, index, and Kibana privileges
- Assign or remove a custom role for a user on a Serverless project using the Cloud API's `application_roles`
- Translate a natural-language access request into invite, role, and API key tasks

## Prerequisites and permissions

| Item                 | Description                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| **EC_API_KEY**       | Cloud API key (set by **cloud-setup**). Required for all operations.                                    |
| **Organization ID**  | Auto-discovered using `GET /organizations`. Do not ask the user for it.                                 |
| **Project endpoint** | Elasticsearch endpoint of a Serverless project. Required only for custom role operations.               |
| **ES credentials**   | API key or credentials with `manage_security` privilege on the project. Required only for custom roles. |
| **Org owner role**   | Only Organization owners can create and manage Cloud API keys. Required for API key operations.         |

Run `python3 skills/cloud/access-management/scripts/cloud_access.py list-members` to verify that `EC_API_KEY` is valid
and auto-discover the org ID before proceeding with any operation.

### Operation-level permissions

The following permissions are required for common access management operations in Elastic Cloud Serverless.

| Operation                          | Required permission                                            |
| ---------------------------------- | -------------------------------------------------------------- |
| Invite / remove members            | Organization owner (`organization-admin`)                      |
| Assign or remove roles             | Organization owner (`organization-admin`)                      |
| Create / revoke Cloud API keys     | Organization owner (`organization-admin`)                      |
| List members, invitations, or keys | Any organization member                                        |
| Create / delete custom roles       | `manage_security` cluster privilege on the project ES endpoint |

This skill does not perform a separate role pre-check. Attempt the requested operation and let the API enforce
authorization. If the API returns an authorization error (for example, `403 Forbidden`), stop and ask the user to verify
the provided API key permissions.

### Manual setup fallback (when cloud-setup is unavailable)

If this skill is installed standalone and `cloud-setup` is not available, instruct the user to configure Cloud
environment variables manually before running commands. Never ask the user to paste API keys in chat.

| Variable                | Required    | Description                                                                                       |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
| `EC_API_KEY`            | Yes         | Elastic Cloud API key with Organization owner role.                                               |
| `EC_BASE_URL`           | No          | Cloud API base URL (default: `https://api.elastic-cloud.com`).                                    |
| `ELASTICSEARCH_URL`     | Conditional | Elasticsearch URL. Required only for custom role operations.                                      |
| `ELASTICSEARCH_API_KEY` | Conditional | Elasticsearch API key with `manage_security` privilege. Required only for custom role operations. |

> **Note:** If `EC_API_KEY` is missing, or the user does not have a Cloud API key yet, direct the user to generate one
> at [Elastic Cloud API keys](https://cloud.elastic.co/account/keys), then configure it locally using the steps below.

Preferred method (agent-friendly): create a `.env` file in the project root:

```bash
EC_API_KEY=your-api-key
EC_BASE_URL=https://api.elastic-cloud.com
# Only needed for custom role operations against the project Elasticsearch endpoint:
# ELASTICSEARCH_URL=https://<project-id>.es.<region>.elastic-cloud.com
# ELASTICSEARCH_API_KEY=<your-es-manage-security-api-key>
```

All `cloud/*` scripts auto-load `.env` from the working directory.

Alternative: export directly in the terminal:

```bash
export EC_API_KEY="<your-cloud-api-key>"
export EC_BASE_URL="https://api.elastic-cloud.com"
# Only needed for custom role operations against the project Elasticsearch endpoint:
# export ELASTICSEARCH_URL="https://<project-id>.es.<region>.elastic-cloud.com"
# export ELASTICSEARCH_API_KEY="<your-es-manage-security-api-key>"
```

Terminal exports may not be visible to sandboxed agents running in separate shell sessions, so prefer `.env` when using
an agent.

## Decomposing Access Requests

When the user describes access in natural language (for example, "add Alice to my search project as a developer"), break
the request into discrete tasks before executing.

### Step 1 — Identify the components

| Component        | Question to answer                                                  |
| ---------------- | ------------------------------------------------------------------- |
| **Who**          | New org member (invite) or existing member (role update)?           |
| **What**         | Which Serverless project(s) or org-level access?                    |
| **Access level** | Predefined role (Admin/Developer/Viewer/Editor) or custom role?     |
| **API key?**     | Does the request also need a Cloud API key for programmatic access? |

### Step 2 — Check if a predefined role fits

Consult the [predefined roles table](#predefined-roles) below. Prefer predefined roles — only create a custom role when
predefined roles do not provide the required granularity.

### Step 3 — Check existing state

Before creating or inviting, check what already exists:

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py list-members
python3 skills/cloud/access-management/scripts/cloud_access.py list-api-keys
```

If the user is already a member, skip the invitation and update their roles instead.

**For API key requests**, only Organization owners can create and manage Cloud API keys. If the authenticated user does
not have the `organization-admin` role, API key operations will fail with a 403 error. Review the existing keys returned
by `list-api-keys`. If an active key already exists **for the same purpose or task** with the required roles and
sufficient remaining lifetime, reuse it instead of creating a new one. Two keys with identical permissions are fine when
they serve different purposes (for example, separate CI pipelines), but creating a second key for the same task is
unnecessary and increases the management burden.

### Step 4 — Run

Run the appropriate command(s) from `skills/cloud/access-management/scripts/cloud_access.py`. Confirm destructive
actions (remove member, revoke key) with the user before executing.

### Step 5 — Verify

After execution, list members or keys again to confirm the change took effect.

## Predefined Roles

### Organization-level roles

| Role               | Cloud API `role_id`  | Description                                |
| ------------------ | -------------------- | ------------------------------------------ |
| Organization owner | `organization-admin` | Full admin over org, deployments, projects |
| Billing admin      | `billing-admin`      | Manage billing details only                |

### Serverless project-level roles

| Role           | Cloud API `role_id` | Available on          | Description                                          |
| -------------- | ------------------- | --------------------- | ---------------------------------------------------- |
| Admin          | `admin`             | Search, Obs, Security | Full project management, superuser on sign-in        |
| Developer      | `developer`         | Search only           | Create indices, API keys, connectors, visualizations |
| Viewer         | `viewer`            | Search, Obs, Security | Read-only access to project data and features        |
| Editor         | `editor`            | Obs, Security         | Configure project features, read-only data indices   |
| Tier 1 analyst | `t1_analyst`        | Security only         | Alert triage, general read, create dashboards        |
| Tier 2 analyst | `t2_analyst`        | Security only         | Alert triage, begin investigations, create cases     |
| Tier 3 analyst | `t3_analyst`        | Security only         | Deep investigation, rules, lists, response actions   |
| SOC manager    | `soc_manager`       | Security only         | Alerts, cases, endpoint policy, response actions     |
| Rule author    | `rule_author`       | Security only         | Detection engineering, rule creation                 |

Project-level roles are assigned during invitation (`POST /organizations/{org_id}/invitations`) or using the role
assignment update (`POST /users/{user_id}/role_assignments`). See
[references/api-reference.md](references/api-reference.md) for the `role_assignments` JSON schema including the
`project` scope.

## Custom Roles (Serverless)

When predefined roles lack the required granularity, create a custom role inside the Serverless project using the
Elasticsearch security API and assign it to users through the Cloud API's `application_roles` field.

> **Security: do not assign a predefined Cloud role separately when using a custom role.** Custom roles implicitly grant
> Viewer-level Cloud access for the project scope. If you also assign `viewer` (or any other predefined role) as a
> separate Cloud role assignment for the same project, the user receives the **union** of both roles when they SSO into
> the project — the Viewer stack role is broader than most custom roles and will override the restrictions you intended.

### How custom role assignment works

- **Predefined roles** (`viewer`, `developer`, `admin`, etc.) are assigned via Cloud APIs (`invite-user`,
  `assign-role`). When the user SSOs into the project, they receive the stack role mapped to their Cloud role (for
  example, Cloud `viewer` maps to the `viewer` stack role).
- **Custom roles** are created in the project via the Elasticsearch security API (`create-custom-role`) and assigned via
  the Cloud API's `application_roles` field (`assign-custom-role`). When `application_roles` is set, the user gets
  **only** the specified custom role on SSO — not the default stack role for their Cloud role.
- The `assign-custom-role` command sets `role_id` to the project-type Viewer role (`elasticsearch-viewer`,
  `observability-viewer`, or `security-viewer`) and sets `application_roles` to the custom role name. This ensures the
  user can see and access the project in the Cloud console but receives only the custom role's restricted permissions
  inside the project.
- Cloud API keys can also use `application_roles` to gain ES/Kibana API access on Serverless projects. See
  [Cloud API Keys — ES and Kibana API Access](#cloud-api-keys--es-and-kibana-api-access) below for details.

### Canonical custom-role onboarding flow

1. Create the custom role in the project (`create-custom-role`).
2. Invite the user to the organization if they are not already a member (`invite-user`). Do **not** include project role
   assignments in the invitation — the custom role assignment in the next step handles project access.
3. Assign the custom role to the user (`assign-custom-role --user-id ... --project-id ... --custom-role-name ...`).
4. Verify with `list-members` and `list-roles`.

### Create a custom role

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py create-custom-role \
  --role-name marketing-analyst \
  --body '{"cluster":[],"indices":[{"names":["marketing-*"],"privileges":["read","view_index_metadata"]}]}'
```

This calls `PUT /_security/role/{name}` on the project Elasticsearch endpoint.

### Naming constraints

Role names must begin with a letter or digit and contain only letters, digits, `_`, `-`, and `.`. Run-as privileges are
not available in Serverless.

### When to use custom roles versus predefined

| Scenario                                   | Use             |
| ------------------------------------------ | --------------- |
| Standard admin/developer/viewer access     | Predefined role |
| Read-only access to specific index pattern | Custom role     |
| DLS or FLS restrictions                    | Custom role     |
| Kibana feature-level access control        | Custom role     |

For advanced DLS/FLS patterns (templated queries, ABAC), see the **elasticsearch-authz** skill.

## Cloud API Keys — ES and Kibana API Access

Cloud API keys can now optionally access Elasticsearch and Kibana APIs on Serverless projects, in addition to the Cloud
API. This enables a single credential for both control plane (Cloud API) and data plane (ES/Kibana API) operations — for
example, a CI pipeline that creates a project via Cloud API and then indexes data via ES API.

### How it works

Add `application_roles` to the key's `role_assignments` at creation time. This field accepts an array of predefined role
names (`admin`, `developer`, `viewer`, and solution-specific roles like `t1_analyst`, `editor`) or custom role names
created in the project via `PUT /_security/role/{name}`. Predefined roles are available in every project by default.
Custom roles must be created individually in each project where the key should have access — if a referenced custom role
does not exist in a project, the key silently gets no access there.

### Critical rule: no implicit inheritance

Unlike users, API keys **never** inherit stack roles from their `role_id`. If `application_roles` is omitted or empty,
the key has Cloud API access only. Calling an ES or Kibana endpoint with such a key returns **403 Forbidden**. This is
by design for backward compatibility — existing keys without `application_roles` continue to work as Cloud-only keys.

### Scoping modes

- **Project-scoped** (preferred) — grants access to specific projects or all projects of a given type. Uses the
  `project` key in `role_assignments` with `application_roles` on each entry. **Use this by default** unless the user
  explicitly needs cross-project access.

- **Organization-scoped** — grants access to **all current and future projects** in the organization. Uses the
  `organization` key in `role_assignments` with `application_roles`. **This is the broadest possible data-plane scope.**
  Only use when the key genuinely needs access to every project (for example, platform automation or cross-project
  search across the whole org). Always confirm with the user before creating an org-scoped key with `application_roles`,
  as it grants ES/Kibana access to projects that may not exist yet.

> **Custom roles and org-scoped access:** When using a custom role name in `application_roles` with organization-scoped
> assignments, the custom role must exist in each project where you want the key to have access. If a project does not
> have that custom role defined (via `PUT /_security/role/{name}`), the key silently gets no access to that project — no
> error is raised. For org-wide access, prefer predefined roles (`admin`, `developer`, `viewer`) which are available in
> every project by default. If you must use custom roles across multiple projects, ensure the role is created in each
> target project first.

> **Agent guidance:** When a user asks for an API key with ES/Kibana access, default to project-scoped assignments. Only
> suggest organization-scoped `application_roles` if the user explicitly needs access across all projects. Confirm the
> intent before proceeding — org-scoped access applies to future projects too. If the user specifies a custom role name
> with org-scoped access, warn them that the role must be defined in each project individually.

### Examples

**Project-scoped key with developer ES access** (using `--stack-access` convenience flag):

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
  --description "CI pipeline - ES ingest" \
  --expiration 30d \
  --roles '{"project":{"elasticsearch":[{"role_id":"developer","organization_id":"$ORG_ID","all":true}]}}' \
  --stack-access developer
```

**Organization-scoped key with admin ES access** (access to ALL projects — use with caution):

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
  --description "Platform automation" \
  --expiration 7d \
  --roles '{"organization":[{"role_id":"organization-admin","organization_id":"$ORG_ID"}]}' \
  --stack-access admin
```

**Project-scoped key with a custom role** (raw JSON):

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
  --description "Marketing ETL" \
  --expiration 14d \
  --roles '{"project":{"elasticsearch":[{"role_id":"elasticsearch-viewer","organization_id":"$ORG_ID","all":false,"project_ids":["$PROJECT_ID"],"application_roles":["marketing-writer"]}]}}'
```

Replace `$ORG_ID` and `$PROJECT_ID` with the actual organization and project IDs. Use `list-members` to discover the org
ID.

> **Common mistake:** If your API key gets a 403 when calling an ES or Kibana endpoint, the most likely cause is missing
> `application_roles`. Unlike users, API keys must have explicit `application_roles` to access the stack — the `role_id`
> alone is not sufficient.

## Examples

### Invite a user as a Viewer on a search project

**Prompt:** "Add `alice@example.com` to my search project with read-only access."

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py invite-user \
  --emails alice@example.com \
  --roles '{"project":{"elasticsearch":[{"role_id":"viewer","organization_id":"$ORG_ID","all":false,"project_ids":["$PROJECT_ID"]}]}}'
```

Replace `$ORG_ID` and `$PROJECT_ID` with the actual IDs. The Viewer role is assigned when the invitation is accepted.
For custom role access, use `assign-custom-role` after the user has accepted the invitation — do not combine a
predefined role assignment with a custom role for the same project.

### Create a CI/CD API key

**Prompt:** "Create an API key for our CI pipeline that expires in 30 days with editor access to all deployments."

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
  --description "CI/CD pipeline" \
  --expiration "30d" \
  --roles '{"deployment":[{"role_id":"deployment-editor","all":true}]}'
```

The actual key value is written to a secure temp file (0600 permissions). The stdout JSON contains a `_secret_file` path
instead of the raw secret. Tell the user to retrieve the key from that file — it is shown only once. When the CI
pipeline no longer needs this key, revoke it using `delete-api-key` to avoid unused keys accumulating.

### Create a CI/CD API key with ES access

**Prompt:** "Create an API key for our CI pipeline that can index data into our search projects."

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
  --description "CI pipeline - ES ingest" \
  --expiration 30d \
  --roles '{"project":{"elasticsearch":[{"role_id":"developer","organization_id":"$ORG_ID","all":true}]}}' \
  --stack-access developer
```

Replace `$ORG_ID` with the actual organization ID. The `--stack-access` flag injects `application_roles: ["developer"]`
into the role assignments, granting the key developer-level ES/Kibana API access on all Elasticsearch projects. Without
`--stack-access` (or explicit `application_roles` in the JSON), the key would only have Cloud API access and receive 403
on ES/Kibana calls.

### Create a custom role for marketing data

**Prompt:** "Create a role that gives read-only access to marketing-\* indices on my search project."

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py create-custom-role \
  --role-name marketing-reader \
  --body '{"cluster":[],"indices":[{"names":["marketing-*"],"privileges":["read","view_index_metadata"]}]}'
```

Then assign the custom role to a user using the `assign-custom-role` command, which sets `application_roles` in the
Cloud API role assignment.

### Full custom-role flow for read-only dashboards

**Prompt:** "Add `bob@example.com` to my search project with read-only dashboard access."

```bash
# 1) Create custom role in the project
python3 skills/cloud/access-management/scripts/cloud_access.py create-custom-role \
  --role-name dashboard-reader \
  --body '{"cluster":[],"indices":[],"applications":[{"application":"kibana-.kibana","privileges":["feature_dashboard.read"],"resources":["*"]}]}'

# 2) Invite user to the organization (no project roles — custom role handles access)
python3 skills/cloud/access-management/scripts/cloud_access.py invite-user \
  --emails bob@example.com

# 3) After invitation is accepted, assign the custom role via application_roles
python3 skills/cloud/access-management/scripts/cloud_access.py assign-custom-role \
  --user-id "$USER_ID" \
  --project-id "$PROJECT_ID" \
  --project-type elasticsearch \
  --custom-role-name dashboard-reader
```

The user receives Viewer-level Cloud access (can see the project in the console) and **only** `dashboard-reader`
permissions when they SSO into the project. Do not also assign `viewer` as a separate Cloud role for this project —
doing so would grant the broader Viewer stack role and override the custom role's restrictions.

### Update a user's project role

**Prompt:** "Promote Bob to admin on our observability project."

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py assign-role \
  --user-id "$USER_ID" \
  --roles '{"project":{"observability":[{"role_id":"admin","organization_id":"$ORG_ID","all":false,"project_ids":["$PROJECT_ID"]}]}}'
```

Replace `$USER_ID`, `$ORG_ID`, and `$PROJECT_ID` with actual values. Use `list-members` to look up the user ID. To
remove a role assignment, use `remove-role-assignment` with the same `--roles` schema.

### List all members and their roles

**Prompt:** "Show me who has access to my organization."

```bash
python3 skills/cloud/access-management/scripts/cloud_access.py list-members
```

The output includes each member's user ID, email, and assigned roles.

## Guidelines

- If `EC_API_KEY` is not set, do not prompt the user — instruct the agent to invoke **cloud-setup** first.
- Always confirm destructive actions (remove member, revoke key) with the user before executing.
- Prefer predefined roles over custom roles when they satisfy the access requirement.
- API keys created here are additional keys for CI/CD, scoped access, or team members. The initial key is managed by
  **cloud-setup**.
- **Secrets are never printed to stdout or stderr.** The script replaces sensitive fields (`key`, `token`,
  `invitation_token`) with a `REDACTED` placeholder in stdout and writes the full unredacted response to a temporary
  file with 0600 (owner-read-only) permissions. The stdout JSON includes a `_secret_file` path pointing to that file.
  **Never attempt to read, extract, or summarize the contents of the secret file.** If the user asks for the key, tell
  them to open the file at the `_secret_file` path. After the user retrieves the secret, advise them to delete the file.
- Cloud API keys inherit roles at creation and cannot be updated — revoke and recreate to change roles.
- **API key hygiene — minimize, scope, and expire:**
  - Before creating a key, always run `list-api-keys` and check whether an existing key **for the same purpose or task**
    already has the required roles and sufficient remaining lifetime. Keys with identical permissions serving different
    purposes (for example, two separate CI pipelines) are legitimate — the goal is to avoid redundant keys for the same
    task.
  - Always set an `--expiration` that matches the intended task lifetime. Short-lived tasks (CI runs, one-time
    migrations) should use short-lived keys (for example, `1d`, `7d`).
  - After a task is complete, prompt the user to revoke any keys that are no longer needed using `delete-api-key`. This
    applies to both short-lived and long-running keys.
  - Long-running keys (for example, monitoring pipelines) should still have a defined expiration and be rotated
    periodically rather than set to never expire.
- Each organization supports up to 500 active API keys. Default expiration is 3 months.
- Invitations expire after 72 hours by default. Resend if the user has not accepted.
- For SAML SSO configuration, refer to
  [Elastic Cloud SAML docs](https://www.elastic.co/docs/deploy-manage/users-roles/cloud-organization/configure-saml-authentication).
- **Custom role security — do not over-assign:** Never assign a predefined Cloud role (for example, `viewer`) for a
  project when using `assign-custom-role` for the same project. The custom role assignment implicitly grants
  Viewer-level Cloud access. Adding a predefined role on top widens the user's in-project permissions beyond what the
  custom role intended.
- If a custom role exists but the user cannot access the project, verify the role was assigned with `assign-custom-role`
  (which uses `application_roles` in the Cloud API). Creating a custom role alone does not grant project access — the
  Cloud API assignment is required.
- For network-level security (traffic filters, private links), see the **cloud-network-security** skill.
- For ES-level role management beyond Cloud roles (native users, DLS/FLS), see **elasticsearch-authz**.

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY