From elastic-agent-skills
Manages Elastic Cloud organization access: invites users, assigns roles to Serverless projects, creates/revokes Cloud API keys. Use for granting, modifying, or auditing user access.
npx claudepluginhub elastic/agent-skills --plugin elastic-securityThis skill uses the workspace's default tool permissions.
Manage identity and access for an Elastic Cloud organization and its Serverless projects: invite users, assign
Conducts multi-round deep research on GitHub repos via API and web searches, generating markdown reports with executive summaries, timelines, metrics, and Mermaid diagrams.
Dynamically discovers and combines enabled skills into cohesive, unexpected delightful experiences like interactive HTML or themed artifacts. Activates on 'surprise me', inspiration, or boredom cues.
Generates images from structured JSON prompts via Python script execution. Supports reference images and aspect ratios for characters, scenes, products, visuals.
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_KEYis set in the environment and the organization context is established. IfEC_API_KEYis 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.
application_roles| 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.
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.
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_KEYis missing, or the user does not have a Cloud API key yet, direct the user to generate one at Elastic Cloud API keys, then configure it locally using the steps below.
Preferred method (agent-friendly): create a .env file in the project root:
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:
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.
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.
| 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? |
Consult the predefined roles table below. Prefer predefined roles — only create a custom role when predefined roles do not provide the required granularity.
Before creating or inviting, check what already exists:
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.
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.
After execution, list members or keys again to confirm the change took effect.
| Role | Cloud API role_id | Description |
|---|---|---|
| Organization owner | organization-admin | Full admin over org, deployments, projects |
| Billing admin | billing-admin | Manage billing details only |
| 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 for the role_assignments JSON schema including the
project scope.
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.
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).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.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.application_roles to gain ES/Kibana API access on Serverless projects. See
Cloud API Keys — ES and Kibana API Access below for details.create-custom-role).invite-user). Do not include project role
assignments in the invitation — the custom role assignment in the next step handles project access.assign-custom-role --user-id ... --project-id ... --custom-role-name ...).list-members and list-roles.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.
Role names must begin with a letter or digit and contain only letters, digits, _, -, and .. Run-as privileges are
not available in Serverless.
| 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 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.
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.
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.
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_roleswith 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 (viaPUT /_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_rolesif 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.
Project-scoped key with developer ES access (using --stack-access convenience flag):
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):
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):
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 explicitapplication_rolesto access the stack — therole_idalone is not sufficient.
Prompt: "Add alice@example.com to my search project with read-only access."
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.
Prompt: "Create an API key for our CI pipeline that expires in 30 days with editor access to all deployments."
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.
Prompt: "Create an API key for our CI pipeline that can index data into our search projects."
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.
Prompt: "Create a role that gives read-only access to marketing-* indices on my search project."
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.
Prompt: "Add bob@example.com to my search project with read-only dashboard access."
# 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.
Prompt: "Promote Bob to admin on our observability project."
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.
Prompt: "Show me who has access to my organization."
python3 skills/cloud/access-management/scripts/cloud_access.py list-members
The output includes each member's user ID, email, and assigned roles.
EC_API_KEY is not set, do not prompt the user — instruct the agent to invoke cloud-setup first.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.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.--expiration that matches the intended task lifetime. Short-lived tasks (CI runs, one-time
migrations) should use short-lived keys (for example, 1d, 7d).delete-api-key. This
applies to both short-lived and long-running keys.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.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.