Skip to content

Backstage Governance and Permissions

This guide covers governance models and permission policies for SkillMeat artifacts exposed through your Backstage catalog integration. It explains how to control who can view, deploy, and modify SkillMeat artifacts within Backstage's permission framework.

Table of Contents

Prerequisites

Before configuring governance policies, ensure you have:

  • Backstage instance running with SkillMeat plugin installed (see Plugin Installation)
  • SkillMeat catalog integration configured (see Catalog Integration)
  • SkillMeat enterprise edition deployed with role-based access control enabled
  • Backstage Permissions Framework enabled in your Backstage app-config
  • System admin access to both Backstage and SkillMeat (for policy configuration)

Verifying Plugin Installation

Confirm the SkillMeat plugin is loaded in your Backstage instance:

# Check plugin is registered in app-config.yaml
grep -A 5 'skillmeat:' app-config.yaml

# Should output:
# skillmeat:
#   baseUrl: https://sam.internal

Verifying Permissions Framework

Verify Backstage Permissions Framework is enabled:

# Check app-config.yaml for permission policy configuration
grep -A 10 'permission:' app-config.yaml

Backstage supports multiple permission backends; SkillMeat integrates with the standard Backstage permission API. See Backstage Permissions Documentation for full framework details.

Backstage Permission Model Overview

Backstage's permission system uses a policy-as-code model where administrators define rules that determine what actions users can perform. SkillMeat integrates with Backstage's permission API to enforce artifact-level access control.

Key Concepts

Resources: SkillMeat artifacts exposed in the Backstage catalog (skills, commands, agents, etc.) are Backstage resources that can be gated by permissions.

Actions: Backstage defines standard actions that can be performed on resources: - catalog:entity:read — View an artifact in the catalog - catalog:entity:write — Modify an artifact (edit metadata, content) - catalog:entity:delete — Delete an artifact from the catalog

Policies: Rules that map identities (users/groups) and resource attributes to allowed actions.

Identity: Backstage resolves user identity from your identity provider (IDP). Users belong to groups (teams) that influence permission decisions.

How SkillMeat Integrates

When a user attempts to:

  1. View a SkillMeat artifact in Backstage → Backstage permission system checks catalog:entity:read permission
  2. Edit artifact details (tags, description, etc.) → Backstage checks catalog:entity:write
  3. Deploy the artifact → SkillMeat backend enforces additional scope-based checks (see SkillMeat RBAC Integration)

The permission check happens at the Backstage layer; the artifact deployment enforces SkillMeat-side RBAC.

SkillMeat RBAC Integration

SkillMeat's role-based access control (RBAC) system operates in parallel with Backstage permissions. Understanding both layers is essential for comprehensive governance.

SkillMeat Roles

SkillMeat defines four hierarchical roles that control organizational access:

Role Scope Permissions
system_admin Enterprise-wide Full control: create/update/delete any artifact, deploy globally, manage users and teams, configure governance
team_admin Team-scoped Manage artifacts within team, manage team membership and roles, view enterprise artifacts (read-only)
team_member Team-scoped Create and modify artifacts in team scope, cannot modify enterprise artifacts
viewer Read-only View shared artifacts, no creation or modification capabilities

SkillMeat Scopes

SkillMeat uses ownership scopes to determine access:

Scope Owner Type Visible To Writable By
user Individual user Owner + system_admin Owner + system_admin
team Team collection Team members + system_admin Team members with owner/admin role + system_admin
enterprise Organization-wide All users (with appropriate roles) system_admin only

Integration Pattern

Backstage permissions determine visibility in the catalog view; SkillMeat RBAC determines what actions are allowed on the artifact:

Backstage Permission Check
User can view artifact in catalog? (catalog:entity:read)
SkillMeat RBAC Check
Can user perform the requested action?
Action allowed/denied

Example: A team_member might have Backstage catalog:entity:read permission on all artifacts, but SkillMeat RBAC blocks them from modifying an enterprise artifact (which only system_admin can edit).

Role and Group Mapping

Map your Backstage identity provider groups to SkillMeat roles to establish role hierarchy and permissions.

Identity Provider Configuration

Your Backstage instance connects to an IDP (e.g., Okta, Google Workspace, Microsoft Entra) that provides:

  • User identity (email, ID)
  • Group membership (team names, roles)

SkillMeat reads these from Backstage's AuthContext and uses them to resolve role and scope.

Mapping Table

Configure your Backstage IDP connector to map groups to SkillMeat roles:

Okta Group Google Workspace Group Microsoft Entra Group SkillMeat Role Scope
engineering-team engineering@company.com Engineering Team team_admin Team
platform-team platform@company.com Platform Team system_admin Enterprise
data-team data@company.com Data Science Team team_admin Team
all-users all-users@company.com All Users viewer Read-only

Configuring Backstage IDP Connector

In your Backstage app-config.yaml, configure the auth provider to map groups to roles:

# Okta example
auth:
  providers:
    okta:
      development:
        clientId: ${OKTA_CLIENT_ID}
        clientSecret: ${OKTA_CLIENT_SECRET}
        audience: ${OKTA_AUDIENCE}
        # Group mapping resolved by Backstage auth resolver
        authorizationUrl: ${OKTA_AUTHORIZATION_URL}

# Google example
auth:
  providers:
    google:
      development:
        clientId: ${GOOGLE_CLIENT_ID}
        clientSecret: ${GOOGLE_CLIENT_SECRET}
        # Groups resolved from Google Workspace directory

See Backstage Auth Providers Documentation for provider-specific setup.

Role Resolution Flow

When a user logs into Backstage:

  1. IDP verifies credentials and returns user identity + groups
  2. Backstage auth resolver maps groups to SkillMeat roles
  3. AuthContext is populated with user_id, roles, and scopes
  4. SkillMeat API requests include this context for permission checks

Policy Configuration

Define policies that control what actions authenticated users can perform. Policies are expressed in Backstage's permission policy language.

Policy Location

Backstage permission policies are configured in:

  • Development: packages/backend/src/plugins/permission.ts (or equivalent in your structure)
  • Production: app-config.yaml or external policy service

Creating a Basic Policy

The SkillMeat Backstage plugin integrates with Backstage's standard catalog permissions. Define policies for these actions:

Example policy (TypeScript):

// packages/backend/src/plugins/permission.ts
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import {
  AuthorizeResult,
  PolicyDecision,
  isResourcePermission,
  isPermission,
} from '@backstage/plugin-permission-common';
import {
  catalogEntityCreatePermission,
  catalogEntityDeletePermission,
  catalogEntityReadPermission,
  catalogEntityWritePermission,
} from '@backstage/plugin-catalog-common';

export const permissionPolicy = async (
  request: PermissionEvaluationResult,
  user: BackstageIdentityResponse,
): Promise<PolicyDecision> => {
  // Allow all authenticated users to read catalog entities
  if (
    isPermission(request, catalogEntityReadPermission) &&
    user.identity
  ) {
    return { result: AuthorizeResult.ALLOW };
  }

  // Only team admins and system admins can write
  if (
    isPermission(request, catalogEntityWritePermission) &&
    user.identity
  ) {
    const roles = user.identity.getOptionalClaims().roles || [];
    if (
      roles.includes('team_admin') ||
      roles.includes('system_admin')
    ) {
      return { result: AuthorizeResult.ALLOW };
    }
    return { result: AuthorizeResult.DENY };
  }

  // Only system admins can delete
  if (
    isPermission(request, catalogEntityDeletePermission) &&
    user.identity
  ) {
    const roles = user.identity.getOptionalClaims().roles || [];
    if (roles.includes('system_admin')) {
      return { result: AuthorizeResult.ALLOW };
    }
    return { result: AuthorizeResult.DENY };
  }

  // Default deny
  return { result: AuthorizeResult.DENY };
};

Resource-Based Policies

For more granular control, policies can check resource attributes (artifact type, owner, etc.):

// Restrict deployment of enterprise artifacts
if (
  isResourcePermission(request, skillmeatArtifactDeployPermission) &&
  user.identity
) {
  const artifact = request.resourceRef;
  const roles = user.identity.getOptionalClaims().roles || [];

  // Only system admins can deploy enterprise artifacts
  if (artifact.owner_type === 'enterprise') {
    return {
      result: roles.includes('system_admin')
        ? AuthorizeResult.ALLOW
        : AuthorizeResult.DENY,
    };
  }

  // Team members can deploy within their team
  const userTeams = user.identity.getOptionalClaims().teams || [];
  if (
    artifact.owner_type === 'team' &&
    userTeams.includes(artifact.owner_id)
  ) {
    return { result: AuthorizeResult.ALLOW };
  }

  return { result: AuthorizeResult.DENY };
}

Policy Examples

Real-world governance patterns for common scenarios.

Example 1: Allow Team Members to Browse, Restrict Deployment

Goal: Team members can view all SkillMeat artifacts in the catalog but cannot deploy enterprise artifacts.

Policy:

// Grant read access to all authenticated users
if (isPermission(request, catalogEntityReadPermission)) {
  return { result: user.identity ? AuthorizeResult.ALLOW : AuthorizeResult.DENY };
}

// Grant write access only to team_admin and system_admin
if (isPermission(request, catalogEntityWritePermission)) {
  const roles = user.identity?.getOptionalClaims().roles || [];
  const canWrite =
    roles.includes('team_admin') ||
    roles.includes('system_admin');
  return {
    result: canWrite ? AuthorizeResult.ALLOW : AuthorizeResult.DENY,
  };
}

// Deployment check (in SkillMeat backend, not Backstage)
// SkillMeat API enforces: only system_admin can deploy enterprise artifacts

Example 2: Restrict Destructive Actions to Admins

Goal: Only system administrators can delete artifacts; prevent accidental deletions.

Policy:

// Only system_admin can delete
if (isPermission(request, catalogEntityDeletePermission)) {
  const roles = user.identity?.getOptionalClaims().roles || [];
  return {
    result: roles.includes('system_admin')
      ? AuthorizeResult.ALLOW
      : AuthorizeResult.DENY,
  };
}

Example 3: Enforce Artifact-Type-Specific Rules

Goal: Only the platform team can deploy MCP servers; other teams can deploy skills and commands.

Policy:

// Check artifact type before allowing deployment
if (isResourcePermission(request, skillmeatArtifactDeployPermission)) {
  const artifact = request.resourceRef;
  const roles = user.identity?.getOptionalClaims().roles || [];
  const teams = user.identity?.getOptionalClaims().teams || [];

  // Only platform team can deploy MCP servers
  if (artifact.artifact_type === 'mcp_server') {
    return {
      result: teams.includes('platform-team')
        ? AuthorizeResult.ALLOW
        : AuthorizeResult.DENY,
    };
  }

  // Other teams can deploy skills/commands
  if (['skill', 'command', 'agent'].includes(artifact.artifact_type)) {
    const isTeamMember = teams.some(
      team => team === artifact.owner_id
    );
    return {
      result: isTeamMember || roles.includes('system_admin')
        ? AuthorizeResult.ALLOW
        : AuthorizeResult.DENY,
    };
  }

  return { result: AuthorizeResult.DENY };
}

Audit Logging

Track who performs what actions on SkillMeat artifacts for compliance and governance.

Where Actions Are Logged

Action SkillMeat Log Backstage Log Notes
View artifact in catalog Not logged (read-only) Backstage audit log (if enabled) Low volume; configure in Backstage if needed
Create artifact ✓ Artifact created event ✓ Entity created event Logged in both systems
Update artifact content ✓ Artifact modified event ✓ Entity updated event Complete history in SkillMeat audit trail
Deploy artifact ✓ Deployment event with target, outcome, enforcement mode ✗ Not automatically logged SkillMeat is source of truth for deployments
Delete artifact ✓ Artifact deleted event ✓ Entity deleted event Permanent record in SkillMeat audit trail
Change permissions ✓ Permission change event (ownership, enforcement toggle) ✗ Backstage policy changes not logged to SkillMeat Check Backstage logs for policy modifications

Accessing the SkillMeat Audit Trail

SkillMeat enterprise edition maintains a complete audit log. Access it via:

Web UI (System Admin only): 1. Log in as system admin 2. Navigate to AdminEnterprise Dashboard 3. Select Audit Trail tab 4. Filter by date, event type, artifact, or actor

API (System Admin only):

curl -X GET "http://localhost:8080/api/v1/enterprise/audit-trail?limit=50&offset=0" \
  -H "Authorization: Bearer $SKILLMEAT_ADMIN_PAT"

Response:

{
  "items": [
    {
      "id": "audit-001",
      "timestamp": "2026-04-20T10:30:00Z",
      "actor_id": "user-123",
      "actor_email": "jane@company.com",
      "action": "artifact_deployed",
      "artifact_id": "skill-456",
      "artifact_name": "data-analysis-skill",
      "target_scope": "team",
      "target_id": "data-team",
      "outcome": "success",
      "details": {
        "enforcement_mode": "silent_override",
        "version_deployed": "v2.1.0"
      }
    }
  ],
  "total": 150,
  "offset": 0,
  "limit": 50
}

Backstage Audit Logging

If Backstage audit logging is enabled, check logs for:

# View Backstage permission decisions
# Location depends on your Backstage setup; typically:
# - Docker: container logs
# - Kubernetes: kubectl logs <backstage-pod>
# - Local dev: console output from yarn start

# Search for SkillMeat-related decisions:
grep -i 'skillmeat\|catalog.*permission' backstage.log

Compliance and Retention

SkillMeat audit trail: - Retention: Configurable; default 90 days (enterprise edition) - Immutable: Audit logs cannot be modified or deleted - Queryable: Filter by date range, actor, artifact, action type

Backstage audit log: - Depends on your Backstage audit plugin configuration - See Backstage Audit Logging for retention and export options

Common Governance Patterns

Pattern 1: Separation of Duties

Objective: Prevent any single person from both creating and deploying sensitive artifacts (compliance requirement).

Implementation:

  1. Create role: artifact_creator (can create but not deploy)
  2. Create role: artifact_deployer (can deploy but not create)
  3. Assign different people to each role

Policy:

if (isResourcePermission(request, skillmeatArtifactDeployPermission)) {
  const roles = user.identity?.getOptionalClaims().roles || [];
  // Only artifact_deployer role can deploy
  return {
    result: roles.includes('artifact_deployer')
      ? AuthorizeResult.ALLOW
      : AuthorizeResult.DENY,
  };
}

if (isPermission(request, catalogEntityWritePermission)) {
  const roles = user.identity?.getOptionalClaims().roles || [];
  // Only artifact_creator role can write
  return {
    result: roles.includes('artifact_creator')
      ? AuthorizeResult.ALLOW
      : AuthorizeResult.DENY,
  };
}

Pattern 2: Approval Workflows

Objective: Require approval before deploying critical artifacts.

Implementation:

  1. Create a pending_deployment status for artifacts
  2. System admin reviews and approves before moving to deployed status
  3. Approval recorded in audit trail

Workflow (manual process, not automated):

  1. Developer creates artifact (status: draft)
  2. Developer marks for review (status: pending_review)
  3. System admin reviews in Backstage UI
  4. If approved, system admin deploys (status: deployed)
  5. Audit trail records: creator, reviewer, approver, timestamps

Pattern 3: Environment-Based Access

Objective: Different permissions for development, staging, and production artifacts.

Implementation:

Tag artifacts with environment: - env:dev — Development artifacts - env:staging — Staging artifacts - env:prod — Production artifacts

Policy:

if (isResourcePermission(request, skillmeatArtifactDeployPermission)) {
  const artifact = request.resourceRef;
  const roles = user.identity?.getOptionalClaims().roles || [];
  const teams = user.identity?.getOptionalClaims().teams || [];

  // Dev artifacts: team members can deploy
  if (artifact.tags.includes('env:dev')) {
    return {
      result: teams.length > 0 ? AuthorizeResult.ALLOW : AuthorizeResult.DENY,
    };
  }

  // Staging artifacts: team admins only
  if (artifact.tags.includes('env:staging')) {
    return {
      result: roles.includes('team_admin')
        ? AuthorizeResult.ALLOW
        : AuthorizeResult.DENY,
    };
  }

  // Production artifacts: system admins only
  if (artifact.tags.includes('env:prod')) {
    return {
      result: roles.includes('system_admin')
        ? AuthorizeResult.ALLOW
        : AuthorizeResult.DENY,
    };
  }

  return { result: AuthorizeResult.DENY };
}

Troubleshooting

Policy Not Being Applied

Symptom: Users can perform actions that should be denied by policy.

Diagnosis:

  1. Verify Backstage Permissions Framework is enabled:

    grep -A 5 'permission:' app-config.yaml
    

  2. Check Backstage logs for permission evaluation errors:

    # Docker
    docker logs <backstage-container> | grep -i permission
    

  3. Verify user has correct roles:

  4. Log in as user
  5. Check browser DevTools → Network → /api/auth/identify response
  6. Verify roles array includes expected roles

Solution: - Restart Backstage after policy changes - Verify IDP is configured correctly (see Role and Group Mapping) - Check that permission policy is registered in backend plugin

User Denied Unexpectedly

Symptom: User denied access to an artifact they should be able to access.

Diagnosis:

  1. Check user's role:

    # Via SkillMeat API (System Admin)
    curl -X GET "http://localhost:8080/api/v1/enterprise/users?email=user@company.com" \
      -H "Authorization: Bearer $SKILLMEAT_ADMIN_PAT"
    

  2. Check team membership:

    curl -X GET "http://localhost:8080/api/v1/enterprise/teams" \
      -H "Authorization: Bearer $SKILLMEAT_ADMIN_PAT" \
      | jq '.[] | select(.members[].email == "user@company.com")'
    

  3. Check artifact ownership and visibility:

    curl -X GET "http://localhost:8080/api/v1/enterprise/artifacts/{artifact_id}" \
      -H "Authorization: Bearer $SKILLMEAT_ADMIN_PAT" \
      | jq '{owner_type, owner_id, is_active}'
    

Solution: - Promote user to higher role if needed - Add user to team if accessing team-owned artifacts - Verify artifact is active (not archived or deleted) - Check Backstage permission policy includes the user's role

Audit Log Missing Entries

Symptom: Expected action not appearing in audit trail.

Diagnosis:

  1. Verify audit logging is enabled:

    # Check SkillMeat config
    curl -X GET "http://localhost:8080/api/v1/admin/config" \
      -H "Authorization: Bearer $SKILLMEAT_ADMIN_PAT" \
      | jq '.audit_logging_enabled'
    

  2. Check action occurred in expected timeframe:

    # Query audit trail with date filter
    curl -X GET "http://localhost:8080/api/v1/enterprise/audit-trail?start_date=2026-04-20T00:00:00Z" \
      -H "Authorization: Bearer $SKILLMEAT_ADMIN_PAT"
    

  3. Verify user has audit read permissions:

  4. User must be system_admin to query audit logs
  5. Regular users cannot access audit trails

Solution: - Enable audit logging in SkillMeat config if disabled - Check that action completed successfully (failures may not be logged) - Verify you're querying the correct date range - Contact system admin if audit logging is disabled

Next Steps