# Openmetadata Ops

> Administer OpenMetadata platform including users, teams, bots, roles, policies, and security. Use when managing access control, configuring SSO, creating service accounts, setting up team hierarchies, or troubleshooting authentication issues.

- Skill: `arustydev/openmetadata-ops` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arustydev/openmetadata-ops`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arustydev/openmetadata-ops/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: aRustyDev (https://skillmd.com/u/arustydev)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/arustydev/openmetadata-ops

---


# OpenMetadata Operations

Guide for administering OpenMetadata platform: users, teams, bots, roles, policies, security configuration, and operational tasks.

## When to Use This Skill

- Managing users and team hierarchies
- Creating and configuring bots (service accounts)
- Setting up roles and policies (RBAC/ABAC)
- Configuring SSO and authentication providers
- Managing JWT tokens and API access
- Troubleshooting authentication and authorization issues

## This Skill Does NOT Cover

- Building OpenMetadata SDKs (see `openmetadata-sdk-dev`)
- Using SDKs/APIs to build integrations (see `openmetadata-dev`)
- Deploying OpenMetadata infrastructure (Kubernetes, Docker)
- Database administration for OpenMetadata backend

---

## Team Hierarchy

### Team Types

OpenMetadata uses a hierarchical team structure:

```
Organization (root)
├── Business Unit
│   ├── Division
│   │   ├── Department
│   │   │   └── Group ← Only Groups can own Data Assets
│   │   └── Group
│   └── Department
│       └── Group
└── Business Unit
    └── Group
```

| Level | Purpose | Can Own Assets |
|-------|---------|----------------|
| **Organization** | Company root, auto-created | No |
| **Business Unit** | Top-tier organizational unit | No |
| **Division** | Mid-tier under Business Unit | No |
| **Department** | Team under Division | No |
| **Group** | Lowest level, contains users | **Yes** |

### Create Team

**Via UI:**
1. Navigate to **Settings → Teams**
2. Click **Add Team**
3. Select team type (BusinessUnit, Division, Department, Group)
4. Set parent team
5. Add team name, display name, description

**Via API:**
```bash
curl -X PUT "http://localhost:8585/api/v1/teams" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "data-engineering",
    "displayName": "Data Engineering",
    "description": "Data platform team",
    "teamType": "Group",
    "parents": [{"id": "parent-team-uuid", "type": "team"}]
  }'
```

### Team Policies

Assign policies to teams for inherited permissions:

```bash
curl -X PATCH "http://localhost:8585/api/v1/teams/${TEAM_ID}" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json-patch+json" \
  -d '[
    {
      "op": "add",
      "path": "/policies/-",
      "value": {"id": "policy-uuid", "type": "policy"}
    }
  ]'
```

---

## User Management

### Add User

**Via UI:**
1. Navigate to **Settings → Users**
2. Click **Add User**
3. Enter email, name, select teams
4. Assign roles

**Via API:**
```bash
curl -X PUT "http://localhost:8585/api/v1/users" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "john.doe",
    "email": "john.doe@company.com",
    "displayName": "John Doe",
    "teams": [{"id": "team-uuid", "type": "team"}],
    "roles": [{"id": "role-uuid", "type": "role"}]
  }'
```

### Assign Role to User

```bash
curl -X PATCH "http://localhost:8585/api/v1/users/${USER_ID}" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json-patch+json" \
  -d '[
    {
      "op": "add",
      "path": "/roles/-",
      "value": {"id": "data-steward-role-uuid", "type": "role"}
    }
  ]'
```

### Generate User Access Token

Users can generate personal access tokens:

1. Go to **Profile → Access Token**
2. Click **Generate New Token**
3. Set expiration (optional)
4. Copy token immediately (shown only once)

---

## Bots (Service Accounts)

### What Are Bots?

Bots are service accounts for automated processes:
- Ingestion pipelines
- External integrations
- Scheduled jobs
- CI/CD automation

### Built-in Bots

| Bot | Purpose | Default Role |
|-----|---------|--------------|
| `ingestion-bot` | Metadata ingestion pipelines | Ingestion Bot Role |
| `automator-bot` | Automation workflows | Automator Role |

### Create Custom Bot

**Via UI:**
1. Navigate to **Settings → Integrations → Bots**
2. Click **Add Bot**
3. Enter bot name and description
4. Select or create role
5. Copy generated JWT token

**Via API:**
```bash
# Create bot
curl -X PUT "http://localhost:8585/api/v1/bots" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ci-cd-bot",
    "displayName": "CI/CD Bot",
    "description": "Bot for CI/CD pipeline metadata updates",
    "botUser": {
      "name": "ci-cd-bot",
      "email": "ci-cd-bot@openmetadata.org",
      "isBot": true
    }
  }'
```

### Get Bot JWT Token

**Via UI:**
1. Navigate to **Settings → Integrations → Bots**
2. Select the bot
3. Click **Revoke & Regenerate Token** or view existing token

**Via API:**
```bash
# Get bot details including auth mechanism
curl "http://localhost:8585/api/v1/bots/name/ingestion-bot?fields=botUser" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}"
```

### Bot Token Validation

Validate bot tokens at [jwt.io](https://jwt.io) or programmatically:

```python
import jwt

def validate_bot_token(token: str, public_key: str) -> dict:
    """Validate and decode bot JWT token."""
    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],
            issuer="open-metadata.org",
        )
        assert payload.get("isBot") == True
        return payload
    except jwt.ExpiredSignatureError:
        raise ValueError("Token expired - regenerate in UI")
    except jwt.InvalidTokenError as e:
        raise ValueError(f"Invalid token: {e}")
```

### Bot Token Rotation

Rotate bot tokens periodically:

1. Navigate to **Settings → Integrations → Bots**
2. Select the bot
3. Click **Revoke & Regenerate Token**
4. Update all systems using the old token
5. Verify ingestion pipelines are working

---

## Roles and Policies

### Access Control Model

OpenMetadata uses hybrid RBAC + ABAC:

```
Authorization Decision = f(User, Resource, Operation)

Where:
- User    = Identity + Roles + Teams
- Resource = Entity Type + Attributes (owner, domain, tags)
- Operation = Create, Edit, Delete, ViewAll, EditOwner, etc.
```

### Built-in Roles

| Role | Description | Key Permissions |
|------|-------------|-----------------|
| **Admin** | Full platform access | All operations on all resources |
| **Data Consumer** | Read-only access | ViewBasic on most entities |
| **Data Steward** | Governance operations | Edit descriptions, tags, glossary |
| **Ingestion Bot Role** | Pipeline operations | Create/edit services and entities |

### Create Custom Role

**Via UI:**
1. Navigate to **Settings → Roles**
2. Click **Add Role**
3. Enter name and description
4. Add policies to the role

**Via API:**
```bash
curl -X PUT "http://localhost:8585/api/v1/roles" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "DataEngineer",
    "displayName": "Data Engineer",
    "description": "Role for data engineering team",
    "policies": [
      {"id": "policy-uuid-1", "type": "policy"},
      {"id": "policy-uuid-2", "type": "policy"}
    ]
  }'
```

### Policy Structure

Policies contain rules that define permissions:

```json
{
  "name": "TableEditPolicy",
  "rules": [
    {
      "name": "AllowEditTables",
      "resources": ["table"],
      "operations": ["Create", "Edit", "Delete"],
      "effect": "allow",
      "condition": "isOwner()"
    }
  ]
}
```

### Common Policy Rules

| Rule | Effect | Description |
|------|--------|-------------|
| `isOwner()` | Condition | User/team owns the resource |
| `inTeam('team-name')` | Condition | User belongs to team |
| `hasDomain('domain')` | Condition | Resource is in domain |
| `hasTag('tag-fqn')` | Condition | Resource has specific tag |

### Create Policy

```bash
curl -X PUT "http://localhost:8585/api/v1/policies" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "TeamOwnershipPolicy",
    "displayName": "Team Ownership Policy",
    "description": "Allow team members to edit owned resources",
    "rules": [
      {
        "name": "EditOwnedResources",
        "resources": ["table", "dashboard", "pipeline"],
        "operations": ["Edit", "EditDescription", "EditTags"],
        "effect": "allow",
        "condition": "isOwner()"
      }
    ]
  }'
```

### Operations Reference

| Operation | Description |
|-----------|-------------|
| `Create` | Create new entities |
| `Delete` | Delete entities |
| `ViewAll` | View all entity fields including sensitive data |
| `ViewBasic` | View basic fields (name, description, tags) |
| `Edit` | Edit entity properties |
| `EditDescription` | Edit description only |
| `EditTags` | Edit tags and classifications |
| `EditOwner` | Change entity owner |
| `EditLineage` | Modify lineage edges |
| `EditCustomFields` | Modify custom properties |

---

## Authentication Configuration

### Supported Providers

| Provider | Type | Notes |
|----------|------|-------|
| OpenMetadata (built-in) | JWT | Development only |
| Google | OIDC | Google Workspace SSO |
| Okta | OIDC | Okta SSO |
| Azure AD | OIDC | Microsoft Entra ID |
| Auth0 | OIDC | Auth0 SSO |
| AWS Cognito | OIDC | AWS Cognito User Pools |
| OneLogin | OIDC | OneLogin SSO |
| Keycloak | OIDC | Self-hosted OIDC |
| Custom OIDC | OIDC | Any OIDC-compliant provider |

> **Note**: OpenMetadata does not support multiple auth providers simultaneously.

### Configure SSO (Example: Okta)

**1. Create Okta Application:**
- Sign in to Okta Admin Console
- Navigate to **Applications → Create App Integration**
- Select **OIDC - OpenID Connect** and **Web Application**
- Set redirect URI: `http://localhost:8585/callback`

**2. Configure OpenMetadata:**

```yaml
# openmetadata.yaml
authenticationConfiguration:
  provider: okta
  publicKeyUrls:
    - https://your-domain.okta.com/oauth2/default/v1/keys
  authority: https://your-domain.okta.com/oauth2/default
  clientId: <your-client-id>
  callbackUrl: http://localhost:8585/callback
```

**3. Configure Frontend:**

```yaml
# openmetadata.yaml (continued)
authorizerConfiguration:
  className: org.openmetadata.service.security.DefaultAuthorizer
  containerRequestFilter: org.openmetadata.service.security.JwtFilter
  adminPrincipals:
    - admin@company.com
  principalDomain: company.com
```

### LDAP/Active Directory

For LDAP integration, use an OIDC proxy like Keycloak:

1. Deploy Keycloak
2. Configure LDAP User Federation in Keycloak
3. Configure OpenMetadata to use Keycloak as OIDC provider

---

## Custom Properties Administration

### Create Custom Property Type

**Via UI:**
1. Navigate to **Settings → Custom Properties**
2. Select entity type (Table, Dashboard, etc.)
3. Click **Add Property**
4. Configure name, type, description

**Via API:**
```bash
curl -X PUT "http://localhost:8585/api/v1/metadata/types/name/table/customProperties" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "dataClassification",
    "description": "Data classification level",
    "propertyType": {
      "id": "enum-type-uuid",
      "type": "type"
    },
    "customPropertyConfig": {
      "config": {
        "values": ["Public", "Internal", "Confidential", "Restricted"]
      }
    }
  }'
```

### List Custom Properties

```bash
curl "http://localhost:8585/api/v1/metadata/types/name/table?fields=customProperties" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}"
```

---

## Troubleshooting

### Authentication Issues

**Problem: 401 Unauthorized**

1. Check JWT token validity:
   ```bash
   # Decode token at jwt.io or:
   echo $JWT_TOKEN | cut -d. -f2 | base64 -d | jq .
   ```

2. Verify token expiration (`exp` claim)

3. Check issuer matches configuration

**Problem: Bot ingestion failing**

1. Verify bot has `Ingestion Bot Role`:
   ```bash
   curl "http://localhost:8585/api/v1/bots/name/ingestion-bot?fields=botUser" \
     -H "Authorization: Bearer ${ADMIN_TOKEN}"
   ```

2. Check role policies weren't modified

3. Regenerate bot token if expired

### Authorization Issues

**Problem: 403 Forbidden**

1. Check user's roles:
   ```bash
   curl "http://localhost:8585/api/v1/users/name/john.doe?fields=roles,teams" \
     -H "Authorization: Bearer ${ADMIN_TOKEN}"
   ```

2. Verify role has required policy

3. Check policy conditions (isOwner, inTeam, etc.)

**Problem: User can't see entities**

1. Verify `ViewBasic` or `ViewAll` permission
2. Check team hierarchy - user must be in a Group
3. Verify entity isn't soft-deleted

### Common Fixes

| Issue | Solution |
|-------|----------|
| Token expired | Regenerate token in UI |
| Missing permissions | Add policy to role |
| User not in team | Add user to Group-type team |
| Bot not working | Check role assignment, regenerate token |
| SSO login failing | Verify redirect URI, check OIDC config |

---

## Operational Tasks

### Backup Considerations

Critical data to back up:
- Database (MySQL/PostgreSQL)
- Elasticsearch indices
- JWT signing keys
- Configuration files

### Audit Logging

OpenMetadata tracks changes via:
- Entity version history
- Change events in Elasticsearch
- API audit logs

Query audit events:
```bash
curl "http://localhost:8585/api/v1/events?entityType=table&timestamp=1704067200000" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}"
```

### Health Checks

```bash
# API health
curl "http://localhost:8585/api/v1/system/health"

# Elasticsearch health
curl "http://localhost:8585/api/v1/search/health"
```

---

## Best Practices

### Security

1. **Never use built-in auth in production** - always configure SSO
2. **Rotate bot tokens** periodically (every 90 days recommended)
3. **Use least-privilege roles** - create specific policies per team
4. **Audit admin access** regularly

### Team Structure

1. **Use Groups for ownership** - only Groups can own assets
2. **Mirror org structure** - Business Unit → Division → Department → Group
3. **Assign domain owners** - use domains for cross-team governance

### Bot Management

1. **Create purpose-specific bots** - don't reuse ingestion-bot
2. **Document bot purposes** in descriptions
3. **Monitor bot token usage** in audit logs
4. **Disable unused bots** rather than deleting

---

## References

- [OpenMetadata Security Guide](https://docs.open-metadata.org/latest/deployment/security)
- [Roles and Policies](https://docs.open-metadata.org/latest/how-to-guides/admin-guide-roles-policies)
- [Team Management](https://docs.open-metadata.org/latest/how-to-guides/admin-guide/teams-and-users)
- [SSO Configuration](https://docs.open-metadata.org/latest/deployment/security)
- `openmetadata-dev` - Using SDKs/APIs for integrations
- `openmetadata-sdk-dev` - Implementing SDKs for new languages

