Policy Reporter logo

Backstage Policy Reporter Plugin

Created by kyverno

Policy Reporter is a service for tracking Kyverno policy results across clusters. It watches PolicyReport resources, exposes a simple API, and offers a UI for triage. That gives you one place to see which rules fail, where, and how often.

The Backstage plugin brings those results into your developer portal. It shows the policies that affect each entity. You can review failing and passing rules at a glance. You can filter by severity or status to focus on what matters now. Teams can jump from a policy name to your own docs to learn how to fix issues. The plugin uses annotations to map services to Kubernetes resources. That keeps the view tied to what runs in each environment.

This project started in the open at VELUX and now lives under the Kyverno org. VELUX describes it as follows. “The Backstage policy reporter plugin integrates Policy Reporter with Backstage to provide a clear and detailed view of Kyverno Policies applied to your entities.”

If your platform already uses Kyverno, this plugin helps surface policy health where engineers work each day. It reduces context switching and makes policy results part of normal app ownership.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install the packages

Run from the Backstage root

Copy
yarn --cwd packages/app add @kyverno/backstage-plugin-policy-reporter
yarn --cwd packages/backend add @kyverno/backstage-plugin-policy-reporter-backend

Add the frontend components

Open packages/app/src/components/catalog/EntityPage.tsx

Add the route for kyverno policies. This puts a new tab on the entity page.

Copy
import React from 'react';
import { EntityLayout } from '@backstage/plugin-catalog';
import { EntityKyvernoPoliciesContent, isPolicyReporterAvailable } from '@kyverno/backstage-plugin-policy-reporter';

// keep your other imports

const serviceEntityPage = (
  <EntityLayout>
    {/* other routes */}

    <EntityLayout.Route
      path="/kyverno"
      title="kyverno policy"
      if={isPolicyReporterAvailable}
    >
      <EntityKyvernoPoliciesContent />
      {/*
        Or use the custom source version
        <EntityCustomPoliciesContent title="Kyverno Policy Reports" sources={['kyverno']} />
      */}
    </EntityLayout.Route>

    {/* other routes */}
  </EntityLayout>
);

export default serviceEntityPage;

Optional link to custom policy docs

Copy
import { EntityKyvernoPoliciesContent } from '@kyverno/backstage-plugin-policy-reporter';

<EntityKyvernoPoliciesContent policyDocumentationUrl="https://docs.example.com/policies.md" />

Add the global Policy Reports page

Add the page route in packages/app/src/App.tsx

Copy
import React from 'react';
import { Route } from 'react-router';
import { FlatRoutes } from '@backstage/core-app-api';
import { PolicyReportsPage } from '@kyverno/backstage-plugin-policy-reporter';

// keep your other imports

const routes = (
  <FlatRoutes>
    {/* existing routes */}

    <Route
      path="/policy-reports"
      element={<PolicyReportsPage title="Policy Reports" />}
    />
  </FlatRoutes>
);

export default routes;

Add a sidebar link in packages/app/src/components/Root/Root.tsx

Copy
import React, { PropsWithChildren } from 'react';
import PolicyIcon from '@material-ui/icons/Policy';
import { Sidebar, SidebarItem, SidebarLogo, SidebarPage, SidebarScrollWrapper } from '@backstage/core-components';

// keep your other imports

export const Root = ({ children }: PropsWithChildren<{}>) => (
  <SidebarPage>
    <Sidebar>
      <SidebarLogo />

      <SidebarScrollWrapper>
        {/* existing sidebar items */}

        <SidebarItem icon={PolicyIcon} to="policy-reports" text="Policy Reports" />
      </SidebarScrollWrapper>
    </Sidebar>
    {children}
  </SidebarPage>
);

Wire the backend with the new backend system

Open packages/backend/src/index.ts

Copy
import { createBackend } from '@backstage/backend-defaults';

// keep your other imports

const backend = createBackend();

// other backend.add calls
backend.add(import('@kyverno/backstage-plugin-policy-reporter-backend'));

backend.start();

Wire the backend with the legacy backend system

Create a plugin file at packages/backend/src/plugins/policy-reporter.ts

Copy
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { createRouter } from '@kyverno/backstage-plugin-policy-reporter-backend';

export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
  return await createRouter({
    logger: env.logger,
    config: env.config,
    discovery: env.discovery,
  });
}

Register the router in packages/backend/src/index.ts

Copy
import { createServiceBuilder } from '@backstage/backend-common';
import { ServerTokenManager } from '@backstage/backend-common';
import createPolicyReporter from './plugins/policy-reporter';

// keep your existing setup

async function main() {
  const logger = /* your logger */;
  const config = /* your config */;
  const discovery = /* your discovery */;
  const router = /* your api router */;

  const policyReporterEnv = {
    logger,
    config,
    discovery,
  };

  router.use(
    '/policy-reporter',
    await createPolicyReporter(policyReporterEnv),
  );

  const service = createServiceBuilder(module)
    .setLogger(logger)
    .addRouter('/api', router);

  await service.start();
}

main().catch(err => {
  /* error handling */
});

Note that frontend and backend must be on matching versions. The backend now expects the environment entity through a query parameter. Do not mix old frontend with new backend or the other way.

Define Kubernetes clusters in the catalog

Create Resource entities for clusters. Add the Policy Reporter endpoint as an annotation on each cluster. Place this YAML in your catalog as fits your setup.

Copy
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: aks-dev
  annotations:
    kyverno.io/endpoint: http://your-domain/policy-reporter/api/
spec:
  type: kubernetes-cluster

Endpoint rules

Repeat for each cluster Resource.

Annotate your services

Add annotations that tell the plugin which Kubernetes objects belong to the entity. Also link the service to the cluster Resources with dependsOn.

Add these to the catalog-info.yaml of each service

Copy
metadata:
  annotations:
    kyverno.io/namespace: default
    kyverno.io/kind: Deployment,Pod
    kyverno.io/resource-name: policy-reporter
spec:
  dependsOn:
    - resource:default/aks-dev
    - resource:default/aks-tst
    - resource:default/aks-qa
    - resource:default/aks-prd

You can set multiple namespaces with a comma. Example default,kyverno

You can set multiple kinds with a comma. Example Deployment,Pod

You can also use the Kubernetes plugin namespace annotation if you already have it

Copy
metadata:
  annotations:
    backstage.io/kubernetes-namespace: default

The kyverno namespace annotation takes priority when both are present.

Pass a policyDocumentationUrl to EntityKyvernoPoliciesContent. The plugin will link each policy chip to documentation using this pattern

DocumentationUrl#PolicyName

Copy
<EntityKyvernoPoliciesContent policyDocumentationUrl="https://docs.example.com/kyverno-policies.md" />

Notes about your Policy Reporter service

Make sure Policy Reporter is reachable from the Backstage backend on the endpoint you set in kyverno.io/endpoint. Use the correct path based on your Policy Reporter deployment.

Changelog

This changelog is produced from commits made to the Policy Reporter plugin since 7 months ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.

Breaking changes

  • Backend endpoint now accepts environment entity as a query parameter. It no longer uses a path parameter. #74 11 days ago
  • Export name is now PolicyReportsPage. It was PolicyReporterPage. Update imports if needed. #63 3 months ago

Features

  • Migrate to a generated API client from openapi. #74 11 days ago
  • PolicyReportsPage shows one table with all failed policies. Add filters for status and severity. #63 3 months ago
  • Add EntityCustomPoliciesContent to view policies from custom sources. #34 6 months ago
  • Add support for the backstage io kubernetes namespace annotation as an alternative to the kyverno io namespace annotation. The kyverno annotation keeps priority. #71 13 days ago
  • Add PolicyReportsPage to give a cluster overview of policy reports. #58 3 months ago

Improvements

  • Show an empty state on PolicyReportsPage when no kubernetes cluster resources are defined. #66 3 months ago
  • Add isPolicyReporterAvailable helper to check if PolicyReporter is configured for an entity. #71 13 days ago

Bug fixes

  • Fix changesets not updating the yarn lock file. #31 6 months ago

Documentation

  • Update docs for the new custom policies component. #34 6 months ago

Dev tooling

  • Add the policy report page to the example app. Bump react router to fix the useLocation error. #62 3 months ago
  • Add app and backend packages to tsconfig include to remove the React UMD warning. #65 3 months ago
  • Add app and backend packages for local testing. Bump Backstage dependencies to v1.39.1. Bring back the Backstage yarn plugin for now. Remove unused config. #49 3 months ago
  • Add code owners file. #38 6 months ago

CI Release

  • Use yarn to publish packages with yarn npm publish. #50 4 months ago
  • Create tags with yarn changeset tag after publish. #52 4 months ago
  • Update the publish script to run yarn changeset tag. Retry a failed release. #54 4 months ago
  • Remove the extra yarn changeset tag step. #56 4 months ago
  • Ignore app and backend packages in the changeset config. #60 3 months ago
  • Add changesets for versioning and publishing. Remove Lerna. #30 6 months ago
  • Install the Backstage yarn plugin and use the Backstage yarn install action in CI. #28 7 months ago

Set up Backstage in minutes with Roadie