IDC User & Group Management

After setting up IDC authentication for your OpenSearch UI application, you need to assign users and groups so they can log in. This guide covers how to manage user and group assignments programmatically using the AWS CLI and SDKs.

How IDC application assignment works

When you create an OpenSearch UI application with IDC enabled, it registers an IAM Identity Center application under the hood. Users must be assigned to this IDC application before they can log in. There are two levels of access:

  • Application assignment — controls who can log in to the OpenSearch UI (managed via sso-admin APIs)
  • Application admin — controls who can edit/delete the app and manage workspaces (managed via opensearch update-application)

Prerequisites

You need three identifiers before managing users:

IdentifierHow to get it
IDC Application ARNFrom aws opensearch get-application
Identity Store IDFrom aws sso-admin list-instances
User/Group IDFrom aws identitystore list-users or list-groups

Get the IDC Application ARN

aws opensearch get-application \
  --id <APP_ID> \
  --region <REGION> \
  --query 'iamIdentityCenterOptions.iamIdentityCenterApplicationArn' \
  --output text

Get the Identity Store ID

aws sso-admin list-instances \
  --region <REGION> \
  --query 'Instances[0].IdentityStoreId' \
  --output text

Managing users

Find a user by email

aws identitystore list-users \
  --identity-store-id <IDENTITY_STORE_ID> \
  --filters '[{"AttributePath":"UserName","AttributeValue":"user@example.com"}]' \
  --region <REGION> \
  --query 'Users[0].UserId' \
  --output text

Add a user to the application

aws sso-admin create-application-assignment \
  --application-arn <IDC_APP_ARN> \
  --principal-id <USER_ID> \
  --principal-type USER \
  --region <REGION>

Remove a user from the application

aws sso-admin delete-application-assignment \
  --application-arn <IDC_APP_ARN> \
  --principal-id <USER_ID> \
  --principal-type USER \
  --region <REGION>

List all assigned users

aws sso-admin list-application-assignments \
  --application-arn <IDC_APP_ARN> \
  --region <REGION>

Managing groups

Find a group by name

aws identitystore list-groups \
  --identity-store-id <IDENTITY_STORE_ID> \
  --filters '[{"AttributePath":"DisplayName","AttributeValue":"MyGroup"}]' \
  --region <REGION> \
  --query 'Groups[0].GroupId' \
  --output text

Add a group to the application

aws sso-admin create-application-assignment \
  --application-arn <IDC_APP_ARN> \
  --principal-id <GROUP_ID> \
  --principal-type GROUP \
  --region <REGION>

Remove a group from the application

aws sso-admin delete-application-assignment \
  --application-arn <IDC_APP_ARN> \
  --principal-id <GROUP_ID> \
  --principal-type GROUP \
  --region <REGION>

Managing application admins

Application admins can edit/delete the OpenSearch UI application and manage workspaces. This is separate from the IDC application assignment above.

Important: The --app-configs parameter is a replace operation — it overwrites all existing admin config keys. Always fetch the current config first with get-application, then include both existing and new entries in your update call.

Check current admins

aws opensearch get-application --id <APP_ID> --region <REGION>

Look for the appConfigs array in the response. It contains the current dashboardAdmin.users and dashboardAdmin.groups values.

Add an IDC user as admin

aws opensearch update-application \
  --id <APP_ID> \
  --app-configs '{
    "key": "opensearchDashboards.dashboardAdmin.users",
    "value": "[\"<IDC_USER_ID>\"]"
  }' \
  --region <REGION>

Add an IDC group as admin

First, look up the group ID by name:

# Get your Identity Store ID
IDENTITY_STORE_ID=$(aws sso-admin list-instances \
  --query 'Instances[0].IdentityStoreId' --output text --region <REGION>)
 
# Find group ID by display name
aws identitystore list-groups \
  --identity-store-id $IDENTITY_STORE_ID \
  --filters '[{"AttributePath":"DisplayName","AttributeValue":"<GROUP_NAME>"}]' \
  --query 'Groups[0].GroupId' \
  --output text \
  --region <REGION>

Then use the group ID in the update call:

aws opensearch update-application \
  --id <APP_ID> \
  --app-configs '{
    "key": "opensearchDashboards.dashboardAdmin.groups",
    "value": "[\"<IDC_GROUP_ID>\"]"
  }' \
  --region <REGION>

Safe workflow: add a group without overwriting existing admins

# 1. Fetch current config
aws opensearch get-application --id <APP_ID> --region <REGION>
 
# 2. Note existing users/groups from the appConfigs response
 
# 3. Update with ALL entries (existing + new)
aws opensearch update-application \
  --id <APP_ID> \
  --app-configs '[
    {"key": "opensearchDashboards.dashboardAdmin.users", "value": "[\"<EXISTING_USER_1>\",\"<EXISTING_USER_2>\"]"},
    {"key": "opensearchDashboards.dashboardAdmin.groups", "value": "[\"<EXISTING_GROUP>\",\"<NEW_GROUP_ID>\"]"}
  ]' \
  --region <REGION>

Python SDK example

Assign a single user by email

Look up a user by their email address and assign them to the OpenSearch UI application.

import boto3
 
region = "us-east-1"
app_id = "your-app-id"
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
# Step 1: Get IDC application ARN
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
 
# Step 2: Get identity store ID
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
# Step 3: Find user by email
users = identity_store.list_users(
    IdentityStoreId=identity_store_id,
    Filters=[{"AttributePath": "UserName", "AttributeValue": "user@example.com"}],
)
user_id = users["Users"][0]["UserId"]
 
# Step 4: Assign user to the application
sso_admin.create_application_assignment(
    ApplicationArn=idc_app_arn,
    PrincipalId=user_id,
    PrincipalType="USER",
)
print(f"Assigned {user_id} to {idc_app_arn}")

Assign a group by name

Look up a group by its display name and assign the entire group. All members of the group get access automatically.

import boto3
 
region = "us-east-1"
app_id = "your-app-id"
group_name = "Analytics Team"
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
# Get IDC application ARN
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
 
# Get identity store ID
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
# Find group by name
groups = identity_store.list_groups(
    IdentityStoreId=identity_store_id,
    Filters=[{"AttributePath": "DisplayName", "AttributeValue": group_name}],
)
group_id = groups["Groups"][0]["GroupId"]
 
# Assign group to the application
sso_admin.create_application_assignment(
    ApplicationArn=idc_app_arn,
    PrincipalId=group_id,
    PrincipalType="GROUP",
)
print(f"Assigned group '{group_name}' ({group_id}) to {idc_app_arn}")

Bulk assign multiple users from a list

Assign a list of users by email in a single script. Skips users that are already assigned or not found.

import boto3
from botocore.exceptions import ClientError
 
region = "us-east-1"
app_id = "your-app-id"
emails = [
    "alice@example.com",
    "bob@example.com",
    "carol@example.com",
]
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
for email in emails:
    # Find user
    users = identity_store.list_users(
        IdentityStoreId=identity_store_id,
        Filters=[{"AttributePath": "UserName", "AttributeValue": email}],
    )
    if not users["Users"]:
        print(f"SKIP {email} — not found in identity store")
        continue
 
    user_id = users["Users"][0]["UserId"]
    try:
        sso_admin.create_application_assignment(
            ApplicationArn=idc_app_arn,
            PrincipalId=user_id,
            PrincipalType="USER",
        )
        print(f"OK   {email} ({user_id})")
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConflictException":
            print(f"SKIP {email} — already assigned")
        else:
            print(f"FAIL {email}{e}")

Bulk assign multiple groups by name

Search for groups by name and assign them all. Useful for onboarding an organization with multiple teams.

import boto3
from botocore.exceptions import ClientError
 
region = "us-east-1"
app_id = "your-app-id"
group_names = [
    "Analytics Team",
    "Security Team",
    "Platform Engineers",
]
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
for name in group_names:
    groups = identity_store.list_groups(
        IdentityStoreId=identity_store_id,
        Filters=[{"AttributePath": "DisplayName", "AttributeValue": name}],
    )
    if not groups["Groups"]:
        print(f"SKIP '{name}' — not found in identity store")
        continue
 
    group_id = groups["Groups"][0]["GroupId"]
    try:
        sso_admin.create_application_assignment(
            ApplicationArn=idc_app_arn,
            PrincipalId=group_id,
            PrincipalType="GROUP",
        )
        print(f"OK   '{name}' ({group_id})")
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConflictException":
            print(f"SKIP '{name}' — already assigned")
        else:
            print(f"FAIL '{name}' — {e}")

List all current assignments

See who currently has access to your application — useful before making changes.

import boto3
 
region = "us-east-1"
app_id = "your-app-id"
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
paginator = sso_admin.get_paginator("list_application_assignments")
for page in paginator.paginate(ApplicationArn=idc_app_arn):
    for a in page["ApplicationAssignments"]:
        principal_type = a["PrincipalType"]
        principal_id = a["PrincipalId"]
        # Resolve name
        if principal_type == "USER":
            user = identity_store.describe_user(
                IdentityStoreId=identity_store_id, UserId=principal_id
            )
            print(f"USER  {user['UserName']}  ({principal_id})")
        else:
            group = identity_store.describe_group(
                IdentityStoreId=identity_store_id, GroupId=principal_id
            )
            print(f"GROUP {group['DisplayName']}  ({principal_id})")

Remove a user by email

import boto3
 
region = "us-east-1"
app_id = "your-app-id"
email = "alice@example.com"
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
users = identity_store.list_users(
    IdentityStoreId=identity_store_id,
    Filters=[{"AttributePath": "UserName", "AttributeValue": email}],
)
user_id = users["Users"][0]["UserId"]
 
sso_admin.delete_application_assignment(
    ApplicationArn=idc_app_arn,
    PrincipalId=user_id,
    PrincipalType="USER",
)
print(f"Removed {email} ({user_id})")

Remove a group by name

import boto3
 
region = "us-east-1"
app_id = "your-app-id"
group_name = "Analytics Team"
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
groups = identity_store.list_groups(
    IdentityStoreId=identity_store_id,
    Filters=[{"AttributePath": "DisplayName", "AttributeValue": group_name}],
)
group_id = groups["Groups"][0]["GroupId"]
 
sso_admin.delete_application_assignment(
    ApplicationArn=idc_app_arn,
    PrincipalId=group_id,
    PrincipalType="GROUP",
)
print(f"Removed group '{group_name}' ({group_id})")

Remove all assignments

Revoke access for all users and groups. Useful when decommissioning or resetting an application.

import boto3
 
region = "us-east-1"
app_id = "your-app-id"
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
 
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
 
paginator = sso_admin.get_paginator("list_application_assignments")
for page in paginator.paginate(ApplicationArn=idc_app_arn):
    for a in page["ApplicationAssignments"]:
        sso_admin.delete_application_assignment(
            ApplicationArn=idc_app_arn,
            PrincipalId=a["PrincipalId"],
            PrincipalType=a["PrincipalType"],
        )
        print(f"Removed {a['PrincipalType']} {a['PrincipalId']}")
 
print("All assignments removed")

Sync from a CSV file

Import users and groups from a CSV file. The CSV should have columns type (USER or GROUP) and name (email or group name).

type,name
USER,alice@example.com
USER,bob@example.com
GROUP,Analytics Team
GROUP,Security Team
import boto3, csv
from botocore.exceptions import ClientError
 
region = "us-east-1"
app_id = "your-app-id"
csv_file = "assignments.csv"
 
opensearch = boto3.client("opensearch", region_name=region)
sso_admin = boto3.client("sso-admin", region_name=region)
identity_store = boto3.client("identitystore", region_name=region)
 
app = opensearch.get_application(id=app_id)
idc_app_arn = app["iamIdentityCenterOptions"]["iamIdentityCenterApplicationArn"]
instances = sso_admin.list_instances()
identity_store_id = instances["Instances"][0]["IdentityStoreId"]
 
with open(csv_file) as f:
    for row in csv.DictReader(f):
        principal_type = row["type"].strip().upper()
        name = row["name"].strip()
 
        # Resolve principal ID
        if principal_type == "USER":
            result = identity_store.list_users(
                IdentityStoreId=identity_store_id,
                Filters=[{"AttributePath": "UserName", "AttributeValue": name}],
            )
            if not result["Users"]:
                print(f"SKIP USER '{name}' — not found")
                continue
            principal_id = result["Users"][0]["UserId"]
        else:
            result = identity_store.list_groups(
                IdentityStoreId=identity_store_id,
                Filters=[{"AttributePath": "DisplayName", "AttributeValue": name}],
            )
            if not result["Groups"]:
                print(f"SKIP GROUP '{name}' — not found")
                continue
            principal_id = result["Groups"][0]["GroupId"]
 
        try:
            sso_admin.create_application_assignment(
                ApplicationArn=idc_app_arn,
                PrincipalId=principal_id,
                PrincipalType=principal_type,
            )
            print(f"OK   {principal_type} '{name}'")
        except ClientError as e:
            if e.response["Error"]["Code"] == "ConflictException":
                print(f"SKIP {principal_type} '{name}' — already assigned")
            else:
                print(f"FAIL {principal_type} '{name}' — {e}")

Create IDC users and groups programmatically

If the user doesn't exist in your IDC identity store yet, you can create them:

Create a user

aws identitystore create-user \
  --identity-store-id <IDENTITY_STORE_ID> \
  --user-name "newuser@example.com" \
  --name '{"GivenName":"Jane","FamilyName":"Doe"}' \
  --display-name "Jane Doe" \
  --emails '[{"Value":"newuser@example.com","Type":"Work","Primary":true}]' \
  --region <REGION>

Create a group

aws identitystore create-group \
  --identity-store-id <IDENTITY_STORE_ID> \
  --display-name "Analytics Team" \
  --region <REGION>

Add a user to a group

aws identitystore create-group-membership \
  --identity-store-id <IDENTITY_STORE_ID> \
  --group-id <GROUP_ID> \
  --member-id '{"UserId":"<USER_ID>"}' \
  --region <REGION>

Add IdC identities to workspace collaborators

After assigning users and groups to your application, you can add them as collaborators on individual workspaces — controlling whether they have read, read/write, or admin access to each workspace.

The workspace collaborators UI has a known search pagination issue that can prevent IdC users and groups from appearing in search results when your identity store is large. Use osui-ws-cli to add collaborators directly via API instead.

# Add a user and a group as workspace admins
osui-ws-cli \
  --endpoint https://<APP_ENDPOINT> \
  --region us-west-2 add \
  --workspace "Production Observability" \
  --user alice@example.com \
  --group "Analytics Team" \
  --access-level admin
 
# Add multiple groups at once
osui-ws-cli \
  --endpoint https://<APP_ENDPOINT> \
  --region us-west-2 add \
  --workspace "SRE Workspace" \
  --group "Platform Engineers" \
  --group "Security Team" \
  --access-level readwrite
 
# Add multiple groups by ID (comma-separated, for large batches)
osui-ws-cli \
  --endpoint https://<APP_ENDPOINT> \
  --region us-west-2 add \
  --workspace-id qFLIUd \
  --group-id "id1,id2,id3,...,idN" \
  --access-level admin
 
# List current collaborators
osui-ws-cli \
  --endpoint https://<APP_ENDPOINT> \
  --region us-west-2 list \
  --workspace "Production Observability"
 
# Remove a group
osui-ws-cli \
  --endpoint https://<APP_ENDPOINT> \
  --region us-west-2 remove \
  --workspace "Production Observability" \
  --group "Analytics Team"

osui-ws-cli resolves user emails and group names to IdC UUIDs automatically and supports adding multiple users and groups in a single command.

Troubleshooting

IssueCauseFix
User can't log in after assignmentIDC assignment propagation delayWait 1–2 minutes and retry
AccessDeniedException on create-application-assignmentMissing sso:CreateApplicationAssignment permissionAdd the IDC permissions to your IAM policy
User not found in list-usersUser not created in IDC identity storeCreate the user first, or check you're querying the correct identity store
ConflictException on assignmentUser already assignedUse list-application-assignments to verify