├── .dockerignore ├── .github ├── ct_chart_schema.yaml ├── ct_lintconf.yaml ├── dependabot.yaml └── workflows │ ├── ci.yaml │ ├── docs.yaml │ ├── helm-chart.yaml │ ├── release-chart.yaml │ └── release-image.yaml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEMO.md ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── charts └── policy-reporter │ ├── Chart.yaml │ ├── README.md │ ├── README.md.gotmpl │ ├── configs │ ├── core.tmpl │ ├── email-reports.tmpl │ ├── kyverno-plugin.tmpl │ ├── trivy-plugin.tmpl │ └── ui.tmpl │ ├── templates │ ├── _helpers.tpl │ ├── cluster-secret.yaml │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── config-email-reports-secret.yaml │ ├── config-secret.yaml │ ├── cronjob-summary-report.yaml │ ├── cronjob-violations-report.yaml │ ├── deployment.yaml │ ├── extra-manifests.yaml │ ├── ingress.yaml │ ├── monitoring │ │ ├── _helpers.tpl │ │ ├── auth-secret.yaml │ │ ├── clusterpolicy-details.dashboard.yaml │ │ ├── clusterpolicy-details.grafanadashboard.yaml │ │ ├── overview.dashboard.yaml │ │ ├── overview.grafanadashboard.yaml │ │ ├── policy-details.dashboard.yaml │ │ ├── policy-details.grafanadashboard.yaml │ │ └── servicemonitor.yaml │ ├── networkpolicy.yaml │ ├── plugins │ │ ├── kyverno │ │ │ ├── _helpers.tpl │ │ │ ├── clusterrole.yaml │ │ │ ├── clusterrolebinding.yaml │ │ │ ├── config-secret.yaml │ │ │ ├── deployment.yaml │ │ │ ├── ingress.yaml │ │ │ ├── networkpolicy.yaml │ │ │ ├── poddisruptionbudget.yaml │ │ │ ├── role.yaml │ │ │ ├── rolebinding.yaml │ │ │ ├── secret-role.yaml │ │ │ ├── secret-rolebinding.yaml │ │ │ ├── service.yaml │ │ │ └── serviceaccount.yaml │ │ └── trivy │ │ │ ├── _helpers.tpl │ │ │ ├── config-secret.yaml │ │ │ ├── deployment.yaml │ │ │ ├── ingress.yaml │ │ │ ├── networkpolicy.yaml │ │ │ ├── poddisruptionbudget.yaml │ │ │ ├── secret-role.yaml │ │ │ ├── secret-rolebinding.yaml │ │ │ ├── service.yaml │ │ │ └── serviceaccount.yaml │ ├── poddisruptionbudget.yaml │ ├── policyreporter.kyverno.io_targetconfigs.yaml │ ├── role.yaml │ ├── rolebinding.yaml │ ├── secret-role.yaml │ ├── secret-rolebinding.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── ui │ │ ├── _helpers.tpl │ │ ├── config-secret.yaml │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── networkpolicy.yaml │ │ ├── poddisruptionbudget.yaml │ │ ├── secret-role.yaml │ │ ├── secret-rolebinding.yaml │ │ ├── service.yaml │ │ └── serviceaccount.yaml │ └── values.yaml ├── cmd ├── root.go ├── run.go ├── send.go ├── send │ ├── summary.go │ └── violations.go └── version.go ├── config └── crds │ └── policyreporter.kyverno.io_targetconfigs.yaml ├── docs ├── ALERTMANAGER_TARGET.md ├── CUSTOM_BOARDS.md ├── EXCEPTIONS.md ├── JIRA_TARGET.md ├── SETUP.md ├── UI_AUTH.md └── images │ ├── cluster-policy-details.png │ ├── custom-boards │ ├── cluster.png │ ├── list.png │ ├── selector.png │ └── source.png │ ├── discord.png │ ├── elasticsearch.png │ ├── exceptions │ ├── exception-dialog.png │ └── resource-list.png │ ├── grafana-loki.png │ ├── ms-teams.png │ ├── policy-details.png │ ├── policy-reporter-ui-log.png │ ├── policy-reports-dashboard.png │ ├── prometheus.png │ ├── screen.png │ └── slack.png ├── examples └── jira-target.yaml ├── go.mod ├── go.sum ├── hack ├── controller-gen │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── markers.go └── main.go ├── main.go ├── manifests ├── README.md ├── policy-reporter-kyverno-ui-ha │ └── install.yaml ├── policy-reporter-kyverno-ui │ └── install.yaml ├── policy-reporter-ui │ └── install.yaml └── policy-reporter │ └── install.yaml ├── pkg ├── api │ ├── healthz.go │ ├── healthz_test.go │ ├── metrics.go │ ├── metrics_test.go │ ├── server.go │ ├── server_test.go │ ├── utils.go │ ├── utils_test.go │ ├── v1 │ │ ├── api.go │ │ ├── api_test.go │ │ ├── model.go │ │ └── model_test.go │ └── v2 │ │ ├── api.go │ │ ├── api_test.go │ │ ├── views.go │ │ └── views_test.go ├── cache │ ├── cache.go │ ├── memory.go │ ├── memory_test.go │ ├── redis.go │ └── redis_test.go ├── config │ ├── config.go │ ├── database_factory.go │ ├── database_factory_test.go │ ├── load.go │ ├── load_test.go │ ├── readinessprobe.go │ ├── readinessprobe_test.go │ ├── resolver.go │ └── resolver_test.go ├── crd │ ├── api │ │ ├── policyreport │ │ │ ├── register.go │ │ │ └── v1alpha2 │ │ │ │ ├── clusterpolicyreport_types.go │ │ │ │ ├── clusterpolicyreport_types_test.go │ │ │ │ ├── common.go │ │ │ │ ├── doc.go │ │ │ │ ├── policyreport_types.go │ │ │ │ ├── policyreport_types_test.go │ │ │ │ ├── register.go │ │ │ │ └── zz_generated.deepcopy.go │ │ └── targetconfig │ │ │ ├── register.go │ │ │ └── v1alpha1 │ │ │ ├── configs.go │ │ │ ├── doc.go │ │ │ ├── targetconfig_types.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.register.go │ └── client │ │ ├── policyreport │ │ ├── clientset │ │ │ └── versioned │ │ │ │ ├── clientset.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake │ │ │ │ ├── clientset_generated.go │ │ │ │ ├── doc.go │ │ │ │ └── register.go │ │ │ │ ├── scheme │ │ │ │ ├── doc.go │ │ │ │ └── register.go │ │ │ │ └── typed │ │ │ │ └── policyreport │ │ │ │ └── v1alpha2 │ │ │ │ ├── clusterpolicyreport.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake │ │ │ │ ├── doc.go │ │ │ │ ├── fake_clusterpolicyreport.go │ │ │ │ ├── fake_policyreport.go │ │ │ │ └── fake_policyreport_client.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── policyreport.go │ │ │ │ └── policyreport_client.go │ │ ├── informers │ │ │ └── externalversions │ │ │ │ ├── factory.go │ │ │ │ ├── generic.go │ │ │ │ ├── internalinterfaces │ │ │ │ └── factory_interfaces.go │ │ │ │ └── policyreport │ │ │ │ ├── interface.go │ │ │ │ └── v1alpha2 │ │ │ │ ├── clusterpolicyreport.go │ │ │ │ ├── interface.go │ │ │ │ └── policyreport.go │ │ └── listers │ │ │ └── policyreport │ │ │ └── v1alpha2 │ │ │ ├── clusterpolicyreport.go │ │ │ ├── expansion_generated.go │ │ │ └── policyreport.go │ │ └── targetconfig │ │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── targetconfig │ │ │ └── v1alpha1 │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_targetconfig.go │ │ │ └── fake_targetconfig_client.go │ │ │ ├── generated_expansion.go │ │ │ ├── targetconfig.go │ │ │ └── targetconfig_client.go │ │ ├── informers │ │ └── externalversions │ │ │ ├── factory.go │ │ │ ├── generic.go │ │ │ ├── internalinterfaces │ │ │ └── factory_interfaces.go │ │ │ └── targetconfig │ │ │ ├── interface.go │ │ │ └── v1alpha1 │ │ │ ├── interface.go │ │ │ └── targetconfig.go │ │ └── listers │ │ └── targetconfig │ │ └── v1alpha1 │ │ ├── expansion_generated.go │ │ └── targetconfig.go ├── database │ ├── builder.go │ ├── bun.go │ ├── model.go │ └── views.go ├── email │ ├── client.go │ ├── client_test.go │ ├── filter.go │ ├── filter_test.go │ ├── functions.go │ ├── functions_test.go │ ├── model.go │ ├── summary │ │ ├── fixtures_test.go │ │ ├── generator.go │ │ ├── generator_test.go │ │ ├── model.go │ │ ├── model_test.go │ │ ├── reporter.go │ │ └── reporter_test.go │ └── violations │ │ ├── fixtures_test.go │ │ ├── generator.go │ │ ├── generator_test.go │ │ ├── model.go │ │ ├── model_test.go │ │ ├── reporter.go │ │ └── reporter_test.go ├── filters │ └── filter.go ├── fixtures │ ├── logger.go │ ├── policy_reports.go │ ├── policy_results.go │ └── target_results.go ├── helper │ ├── chunk_slice.go │ ├── chunk_slice_test.go │ ├── first.go │ ├── first_test.go │ ├── http.go │ ├── http_test.go │ ├── title.go │ ├── title_test.go │ ├── utils.go │ └── utils_test.go ├── kubernetes │ ├── cache.go │ ├── debouncer.go │ ├── debouncer_test.go │ ├── fixtures_test.go │ ├── jobs │ │ └── client.go │ ├── namespaces │ │ ├── client.go │ │ └── client_test.go │ ├── pods │ │ └── client.go │ ├── policy_report_client.go │ ├── policy_report_client_test.go │ ├── queue.go │ ├── retry.go │ ├── retry_test.go │ └── secrets │ │ ├── client.go │ │ ├── client_test.go │ │ ├── informer.go │ │ └── informer_test.go ├── leaderelection │ ├── client.go │ └── client_test.go ├── listener │ ├── cleanup.go │ ├── cleanup_test.go │ ├── fixture_test.go │ ├── metrics.go │ ├── metrics │ │ ├── cache.go │ │ ├── cluster_result_detailed.go │ │ ├── cluster_result_detailed_test.go │ │ ├── filter.go │ │ ├── filter_test.go │ │ ├── fixtures_test.go │ │ ├── model.go │ │ ├── model_test.go │ │ ├── result_custom.go │ │ ├── result_custom_test.go │ │ ├── result_detailed.go │ │ └── result_detailed_test.go │ ├── metrics_test.go │ ├── new_result.go │ ├── new_result_test.go │ ├── scope_results.go │ ├── scope_results_test.go │ ├── send_result.go │ ├── send_result_test.go │ ├── store.go │ ├── store_test.go │ └── sync_results.go ├── report │ ├── client.go │ ├── meta_filter.go │ ├── meta_filter_test.go │ ├── model.go │ ├── model_test.go │ ├── publisher.go │ ├── publisher_test.go │ ├── report_filter.go │ ├── report_filter_test.go │ ├── result │ │ ├── id_generator.go │ │ ├── id_generator_test.go │ │ ├── reconditioner.go │ │ ├── reconditioner_test.go │ │ ├── resource.go │ │ └── resource_test.go │ ├── result_filter.go │ ├── result_filter_test.go │ ├── source_filter.go │ ├── source_filter_test.go │ ├── store.go │ └── store_test.go ├── target │ ├── alertmanager │ │ ├── alertmanager.go │ │ ├── alertmanager_test.go │ │ └── client.go │ ├── client.go │ ├── client_test.go │ ├── collection.go │ ├── collection_test.go │ ├── discord │ │ ├── discord.go │ │ └── discord_test.go │ ├── elasticsearch │ │ ├── elasticsearch.go │ │ └── elasticsearch_test.go │ ├── factory.go │ ├── factory │ │ ├── factory.go │ │ └── factory_test.go │ ├── factory_test.go │ ├── formatting │ │ ├── resource.go │ │ └── resource_test.go │ ├── gcs │ │ ├── gcs.go │ │ └── gcs_test.go │ ├── googlechat │ │ ├── googlechat.go │ │ └── googlechat_test.go │ ├── http │ │ ├── logroundtripper.go │ │ ├── logroundtripper_test.go │ │ ├── model.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── jira │ │ ├── jira.go │ │ └── jira_test.go │ ├── kinesis │ │ ├── kinesis.go │ │ └── kinesis_test.go │ ├── loki │ │ ├── loki.go │ │ └── loki_test.go │ ├── provider │ │ ├── aws │ │ │ ├── aws.go │ │ │ └── aws_test.go │ │ └── gcs │ │ │ └── gcs.go │ ├── s3 │ │ ├── s3.go │ │ └── s3_test.go │ ├── securityhub │ │ ├── securityhub.go │ │ └── securityhub_test.go │ ├── slack │ │ ├── slack.go │ │ └── slack_test.go │ ├── splunk │ │ ├── splunk.go │ │ └── splunk_test.go │ ├── teams │ │ ├── card.go │ │ ├── teams.go │ │ └── teams_test.go │ ├── telegram │ │ ├── telegram.go │ │ └── telegram_test.go │ └── webhook │ │ ├── webhook.go │ │ └── webhook_test.go ├── targetconfig │ ├── client.go │ └── client_test.go └── validate │ ├── model.go │ ├── model_test.go │ ├── validate.go │ └── validate_test.go ├── policy.yaml ├── scripts ├── boilerplate.go.txt └── kind.yaml ├── tc.yaml ├── templates ├── summary.html └── violations.html └── test └── alertmanager ├── README.md └── main.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .deploy 2 | config.yaml 3 | build 4 | README.md 5 | docs 6 | **/test.db 7 | sqlite-database.db 8 | values.yaml -------------------------------------------------------------------------------- /.github/ct_chart_schema.yaml: -------------------------------------------------------------------------------- 1 | name: str() 2 | home: str(required=False) 3 | version: str() 4 | type: str(required=False) 5 | apiVersion: str() 6 | appVersion: any(str(), num()) 7 | description: str() 8 | keywords: list(str(), required=False) 9 | sources: list(str(), required=False) 10 | maintainers: list(include('maintainer'), required=False) 11 | icon: str(required=False) 12 | engine: str(required=False) 13 | condition: str(required=False) 14 | tags: str(required=False) 15 | deprecated: bool(required=False) 16 | kubeVersion: str(required=False) 17 | annotations: map(str(), str(), required=False) 18 | dependencies: list(include('dependency'), required=False) 19 | --- 20 | maintainer: 21 | name: str(required=False) 22 | email: str(required=False) 23 | url: str(required=False) 24 | --- 25 | dependency: 26 | name: str() 27 | version: str() 28 | repository: str(required=False) 29 | condition: str(required=False) 30 | tags: list(str(), required=False) 31 | enabled: bool(required=False) 32 | import-values: any(list(str()), list(include('import-value')), required=False) 33 | alias: str(required=False) 34 | --- 35 | import-value: 36 | child: str() 37 | parent: str() 38 | -------------------------------------------------------------------------------- /.github/ct_lintconf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | braces: 4 | min-spaces-inside: 0 5 | max-spaces-inside: 0 6 | min-spaces-inside-empty: -1 7 | max-spaces-inside-empty: -1 8 | brackets: 9 | min-spaces-inside: 0 10 | max-spaces-inside: 0 11 | min-spaces-inside-empty: -1 12 | max-spaces-inside-empty: -1 13 | colons: 14 | max-spaces-before: 0 15 | max-spaces-after: 1 16 | commas: 17 | max-spaces-before: 0 18 | min-spaces-after: 1 19 | max-spaces-after: 1 20 | comments: 21 | require-starting-space: true 22 | min-spaces-from-content: 1 23 | document-end: disable 24 | document-start: disable # No --- to start a file 25 | empty-lines: 26 | max: 2 27 | max-start: 0 28 | max-end: 1 29 | hyphens: 30 | max-spaces-after: 1 31 | indentation: 32 | spaces: consistent 33 | indent-sequences: whatever # - list indentation will handle both indentation and without 34 | check-multi-line-strings: false 35 | key-duplicates: enable 36 | line-length: disable # Lines can be any length 37 | new-line-at-end-of-file: enable 38 | new-lines: 39 | type: unix 40 | trailing-spaces: enable 41 | truthy: 42 | level: warning 43 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - gh-pages 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 14 | 15 | - name: Setup node env 16 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 #v4.4.0 17 | with: 18 | node-version: 16 19 | 20 | - name: Docs 21 | run: cd docs 22 | 23 | - name: Install dependencies 24 | run: npm install 25 | 26 | - name: Generate 27 | run: npm run generate 28 | 29 | - name: Copy Helm files 30 | run: | 31 | cd .. 32 | cp index.yaml ./dist/index.yaml 33 | cp artifacthub-repo.yml ./dist/artifacthub-repo.yml 34 | 35 | - name: Deploy 36 | uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e #v4.0.0 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | publish_dir: ./dist 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/helm-chart.yaml: -------------------------------------------------------------------------------- 1 | name: Lint helm chart 2 | 3 | on: 4 | push: 5 | # run pipeline on push on master 6 | branches: 7 | - main 8 | paths: 9 | - "charts/**" 10 | 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | with: 22 | fetch-depth: "0" 23 | 24 | - name: chart-testing (ct lint) 25 | uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0 26 | 27 | - name: Run Helm Chart lint 28 | run: | 29 | set -e 30 | ct lint --lint-conf=.github/ct_lintconf.yaml \ 31 | --chart-yaml-schema=.github/ct_chart_schema.yaml \ 32 | --target-branch=main \ 33 | --validate-maintainers=false \ 34 | --check-version-increment=false \ 35 | --chart-dirs charts 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .go-version 2 | .deploy 3 | .vscode 4 | /config.yaml 5 | build 6 | /test.yaml 7 | *.db 8 | values*.yaml 9 | monitoring.yaml 10 | coverage.out* 11 | heap* 12 | /.env* 13 | .tools 14 | /.idea/ 15 | .gopath -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Areas of code where owners always wants to be a reviewer/notified. 2 | /charts @fjogeleit 3 | /docs @fjogeleit 4 | /pkg/api @fjogeleit 5 | /pkg/config @fjogeleit 6 | /pkg/cache @fjogeleit 7 | /pkg/database @fjogeleit 8 | /pkg/email @fjogeleit 9 | /pkg/filters @fjogeleit 10 | /pkg/report @fjogeleit 11 | /pkg/target @fjogeleit 12 | /test @fjogeleit 13 | /templates @fjogeleit -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Kyverno Community Code of Conduct v1.0 2 | 3 | ## Contributor Code of Conduct 4 | 5 | As contributors and maintainers of this project, and in the interest of fostering 6 | an open and welcoming community, we pledge to respect all people who contribute 7 | through reporting issues, posting feature requests, updating documentation, 8 | submitting pull requests or patches, and other activities. 9 | 10 | We are committed to making participation in this project a harassment-free experience for 11 | everyone, regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, 13 | religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 22 | * Other unethical or unprofessional conduct. 23 | 24 | Project maintainers have the right and responsibility to remove, edit, or reject 25 | comments, commits, code, wiki edits, issues, and other contributions that are not 26 | aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers 27 | commit themselves to fairly and consistently applying these principles to every aspect 28 | of managing this project. Project maintainers who do not follow or enforce the Code of 29 | Conduct may be permanently removed from the project team. 30 | 31 | This code of conduct applies both within project spaces and in public spaces 32 | when an individual is representing the project or its community. 33 | 34 | Instances of abusive, harassing, or otherwise unacceptable behavior in Kubernetes may be reported by contacting the project maintainer(s). 35 | 36 | This Code of Conduct is adapted from the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md) and the [Contributor Covenant](https://www.contributor-covenant.org/), [version 1.2.0](https://www.contributor-covenant.org/version/1/2/0/code-of-conduct/). 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 AS builder 2 | 3 | ARG LD_FLAGS='-s -w -linkmode external -extldflags "-static"' 4 | ARG TARGETPLATFORM 5 | 6 | WORKDIR /app 7 | 8 | RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \ 9 | export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) 10 | 11 | COPY go.* ./ 12 | RUN go env && go mod download 13 | 14 | COPY . . 15 | 16 | RUN CGO_ENABLED=1 go build -ldflags="${LD_FLAGS}" -tags="sqlite_unlock_notify" -o /app/build/policyreporter -v 17 | 18 | FROM scratch 19 | LABEL MAINTAINER="Frank Jogeleit " 20 | 21 | WORKDIR /app 22 | 23 | USER 1234 24 | 25 | COPY --from=builder /app/LICENSE.md . 26 | COPY --from=builder /app/templates /app/templates 27 | COPY --from=builder /app/build/policyreporter /app/policyreporter 28 | # copy the debian's trusted root CA's to the final image 29 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 30 | 31 | EXPOSE 8080 32 | 33 | ENTRYPOINT ["/app/policyreporter", "run"] 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Frank Jogeleit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Policy Reporter 3.x 2 | [![CI](https://github.com/kyverno/policy-reporter/actions/workflows/ci.yaml/badge.svg)](https://github.com/kyverno/policy-reporter/actions/workflows/ci.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/kyverno/policy-reporter)](https://goreportcard.com/report/github.com/kyverno/policy-reporter) [![Coverage Status](https://coveralls.io/repos/github/kyverno/policy-reporter/badge.svg?branch=main)](https://coveralls.io/github/kyverno/policy-reporter?branch=main) 3 | 4 | 5 | ![Screenshot Policy Reporter UI v2](https://github.com/kyverno/policy-reporter/blob/main/docs/images/screen.png) 6 | 7 | ## Documentation 8 | 9 | The documentation for Policy Reporter v3 can be found here: [https://kyverno.github.io/policy-reporter-docs/](https://kyverno.github.io/policy-reporter-docs/) 10 | 11 | ## Getting Started 12 | 13 | ## Installation with Helm v3 14 | 15 | Installation via Helm Repository 16 | 17 | ### Add the Helm repository 18 | ```bash 19 | helm repo add policy-reporter https://kyverno.github.io/policy-reporter 20 | helm repo update 21 | ``` 22 | 23 | ### Installation with Policy Reporter UI and Kyverno Plugin enabled 24 | ```bash 25 | helm install policy-reporter policy-reporter/policy-reporter --create-namespace -n policy-reporter --set ui.enabled=true --set kyverno-plugin.enabled=true 26 | kubectl port-forward service/policy-reporter-ui 8082:8080 -n policy-reporter 27 | ``` 28 | 29 | Open `http://localhost:8082/` in your browser. -------------------------------------------------------------------------------- /charts/policy-reporter/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: policy-reporter 3 | description: | 4 | Policy Reporter watches for PolicyReport Resources. 5 | It creates Prometheus Metrics and can send rule validation events to different targets like Loki, Elasticsearch, Slack or Discord 6 | 7 | type: application 8 | version: 3.1.4 9 | appVersion: 3.1.1 10 | 11 | icon: https://github.com/kyverno/kyverno/raw/main/img/logo.png 12 | home: https://kyverno.github.io/policy-reporter 13 | sources: 14 | - https://github.com/kyverno/policy-reporter 15 | maintainers: 16 | - name: Frank Jogeleit 17 | -------------------------------------------------------------------------------- /charts/policy-reporter/README.md.gotmpl: -------------------------------------------------------------------------------- 1 | {{ template "chart.header" . }} 2 | {{ template "chart.deprecationWarning" . }} 3 | {{ template "chart.description" . }} 4 | 5 | {{ template "chart.badgesSection" . }} 6 | 7 | ## Documentation 8 | 9 | You can find detailed Information and Screens about Features and Configurations in the [Documentation](https://kyverno.github.io/policy-reporter-docs). 10 | 11 | ## Installation with Helm v3 12 | 13 | Installation via Helm Repository 14 | 15 | ### Add the Helm repository 16 | ```bash 17 | helm repo add policy-reporter https://kyverno.github.io/policy-reporter 18 | helm repo update 19 | ``` 20 | 21 | ### Basic Installation 22 | 23 | The basic installation provides an Prometheus Metrics Endpoint and different REST APIs, for more details have a look at the [Documentation](https://kyverno.github.io/policy-reporter/guide/02-getting-started). 24 | 25 | ```bash 26 | helm install policy-reporter policy-reporter/policy-reporter -n policy-reporter --create-namespace 27 | ``` 28 | 29 | ## Policy Reporter UI 30 | 31 | You can use the Policy Reporter as standalone Application along with the optional UI SubChart. 32 | 33 | ### Installation with Policy Reporter UI and Kyverno Plugin enabled 34 | 35 | ```bash 36 | helm install policy-reporter policy-reporter/policy-reporter --set plugin.kyverno.enabled=true --set ui.enabled=true -n policy-reporter --create-namespace 37 | kubectl port-forward service/policy-reporter-ui 8082:8080 -n policy-reporter 38 | ``` 39 | Open `http://localhost:8082/` in your browser. 40 | 41 | 42 | {{ template "chart.valuesSection" . }} 43 | 44 | {{ template "chart.sourcesSection" . }} 45 | 46 | {{ template "chart.requirementsSection" . }} 47 | 48 | {{ template "chart.maintainersSection" . }} 49 | 50 | {{ template "helm-docs.versionFooter" . }} 51 | -------------------------------------------------------------------------------- /charts/policy-reporter/configs/email-reports.tmpl: -------------------------------------------------------------------------------- 1 | emailReports: 2 | clusterName: {{ .Values.emailReports.clusterName }} 3 | titlePrefix: {{ .Values.emailReports.titlePrefix }} 4 | {{- with .Values.emailReports.smtp }} 5 | smtp: 6 | {{- toYaml . | nindent 4 }} 7 | {{- end }} 8 | 9 | summary: 10 | {{- with .Values.emailReports.summary.to }} 11 | to: 12 | {{- toYaml . | nindent 6 }} 13 | {{- end }} 14 | {{- with .Values.emailReports.summary.filter }} 15 | filter: 16 | {{- toYaml . | nindent 6 }} 17 | {{- end }} 18 | {{- with .Values.emailReports.summary.channels }} 19 | channels: 20 | {{- toYaml . | nindent 6 }} 21 | {{- end }} 22 | 23 | violations: 24 | {{- with .Values.emailReports.violations.to }} 25 | to: 26 | {{- toYaml . | nindent 6 }} 27 | {{- end }} 28 | {{- with .Values.emailReports.violations.filter }} 29 | filter: 30 | {{- toYaml . | nindent 6 }} 31 | {{- end }} 32 | {{- with .Values.emailReports.violations.channels }} 33 | channels: 34 | {{- toYaml . | nindent 6 }} 35 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/configs/kyverno-plugin.tmpl: -------------------------------------------------------------------------------- 1 | leaderElection: 2 | enabled: {{ gt (int .Values.plugin.kyverno.replicaCount) 1 }} 3 | releaseOnCancel: {{ .Values.plugin.kyverno.leaderElection.releaseOnCancel }} 4 | leaseDuration: {{ .Values.plugin.kyverno.leaderElection.leaseDuration }} 5 | renewDeadline: {{ .Values.plugin.kyverno.leaderElection.renewDeadline }} 6 | retryPeriod: {{ .Values.plugin.kyverno.leaderElection.retryPeriod }} 7 | lockName: {{ .Values.plugin.kyverno.leaderElection.lockName }} 8 | 9 | logging: 10 | api: {{ .Values.plugin.kyverno.logging.api }} 11 | server: {{ .Values.plugin.kyverno.logging.server }} 12 | encoding: {{ .Values.plugin.kyverno.logging.encoding }} 13 | logLevel: {{ .Values.plugin.kyverno.logging.logLevel }} 14 | 15 | server: 16 | basicAuth: 17 | username: {{ .Values.basicAuth.username }} 18 | password: {{ .Values.basicAuth.password }} 19 | secretRef: {{ .Values.basicAuth.secretRef }} 20 | 21 | core: 22 | host: {{ printf "http://%s:%d" (include "policyreporter.fullname" .) (.Values.service.port | int) }} 23 | 24 | {{- with .Values.plugin.kyverno.blockReports }} 25 | blockReports: 26 | {{- toYaml . | nindent 4 }} 27 | {{- end }} 28 | -------------------------------------------------------------------------------- /charts/policy-reporter/configs/trivy-plugin.tmpl: -------------------------------------------------------------------------------- 1 | logging: 2 | api: {{ .Values.plugin.trivy.logging.api }} 3 | server: {{ .Values.plugin.trivy.logging.server }} 4 | encoding: {{ .Values.plugin.trivy.logging.encoding }} 5 | logLevel: {{ .Values.plugin.trivy.logging.logLevel }} 6 | 7 | server: 8 | basicAuth: 9 | username: {{ .Values.basicAuth.username }} 10 | password: {{ .Values.basicAuth.password }} 11 | secretRef: {{ .Values.basicAuth.secretRef }} 12 | 13 | core: 14 | host: {{ printf "http://%s:%d" (include "policyreporter.fullname" .) (.Values.service.port | int) }} 15 | skipTLS: {{ .Values.plugin.trivy.policyReporter.skipTLS }} 16 | certificate: {{ .Values.plugin.trivy.policyReporter.certificate }} 17 | secretRef: {{ .Values.plugin.trivy.policyReporter.secretRef }} 18 | basicAuth: 19 | username: {{ .Values.basicAuth.username }} 20 | password: {{ .Values.basicAuth.password }} 21 | 22 | trivy: 23 | dbDir: /db 24 | api: 25 | disable: {{ .Values.plugin.trivy.cveawg.disable }} 26 | 27 | github: 28 | token: {{ .Values.plugin.trivy.github.token | quote }} 29 | disable: {{ .Values.plugin.trivy.github.disable }} 30 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/cluster-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "ui.fullname" . }}-default-cluster 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | {{- if .Values.annotations }} 8 | annotations: 9 | {{- toYaml .Values.annotations | nindent 4 }} 10 | {{- end }} 11 | labels: 12 | {{- include "policyreporter.labels" . | nindent 4 }} 13 | type: Opaque 14 | data: 15 | {{- $username := .Values.basicAuth.username }} 16 | {{- $password := .Values.basicAuth.password }} 17 | {{- $secretRef := .Values.basicAuth.secretRef }} 18 | host: {{ printf "http://%s:%d" (include "policyreporter.fullname" .) (.Values.service.port | int) | b64enc }} 19 | {{- if $username }} 20 | username: {{ $username | b64enc }} 21 | {{- end }} 22 | {{- if $password }} 23 | password: {{ $password | b64enc }} 24 | {{- end }} 25 | {{- if $secretRef }} 26 | secretRef: {{ $secretRef | b64enc }} 27 | {{- end }} 28 | {{- if .Values.plugin.kyverno.enabled }} 29 | {{- $host := printf "http://%s:%d" (include "kyverno-plugin.fullname" .) (.Values.plugin.kyverno.service.port | int) }} 30 | plugin.kyverno: {{ (printf "{\"host\":\"%s\", \"name\":\"kyverno\", \"username\":\"%s\", \"password\":\"%s\"}" $host $username $password) | b64enc }} 31 | {{- end }} 32 | {{- if .Values.plugin.trivy.enabled }} 33 | {{- $host := printf "http://%s:%d/vulnr" (include "trivy-plugin.fullname" .) (.Values.plugin.trivy.service.port | int) }} 34 | plugin.trivy: {{ (printf "{\"host\":\"%s\", \"name\":\"Trivy Vulnerability\", \"username\":\"%s\", \"password\":\"%s\"}" $host $username $password) | b64enc }} 35 | {{- end }} 36 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.enabled -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | {{- if .Values.annotations }} 6 | annotations: 7 | {{- toYaml .Values.annotations | nindent 4 }} 8 | {{- end }} 9 | labels: 10 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 11 | {{- include "policyreporter.labels" . | nindent 4 }} 12 | name: {{ include "policyreporter.fullname" . }} 13 | rules: 14 | - apiGroups: 15 | - '*' 16 | resources: 17 | - policyreports 18 | - policyreports/status 19 | - clusterpolicyreports 20 | - clusterpolicyreports/status 21 | verbs: 22 | - get 23 | - list 24 | - watch 25 | - apiGroups: 26 | - '' 27 | resources: 28 | - namespaces 29 | verbs: 30 | - list 31 | - apiGroups: 32 | - policyreporter.kyverno.io 33 | resources: 34 | - targetconfigs 35 | verbs: 36 | - get 37 | - list 38 | - watch 39 | - apiGroups: 40 | - '' 41 | resources: 42 | - pods 43 | verbs: 44 | - get 45 | - apiGroups: 46 | - 'batch' 47 | resources: 48 | - jobs 49 | verbs: 50 | - get 51 | {{- end -}} 52 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.enabled -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ include "policyreporter.fullname" . }} 6 | {{- if .Values.annotations }} 7 | annotations: 8 | {{- toYaml .Values.annotations | nindent 4 }} 9 | {{- end }} 10 | labels: 11 | {{- include "policyreporter.labels" . | nindent 4 }} 12 | roleRef: 13 | kind: ClusterRole 14 | name: {{ include "policyreporter.fullname" . }} 15 | apiGroup: rbac.authorization.k8s.io 16 | subjects: 17 | - kind: "ServiceAccount" 18 | name: {{ include "policyreporter.serviceAccountName" . }} 19 | namespace: {{ include "policyreporter.namespace" . }} 20 | {{- end -}} 21 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/config-email-reports-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if or .Values.emailReports.summary.enabled .Values.emailReports.violations.enabled }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "policyreporter.fullname" . }}-config-email-reports 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | {{- if .Values.annotations }} 8 | annotations: 9 | {{- toYaml .Values.annotations | nindent 4 }} 10 | {{- end }} 11 | labels: 12 | {{- include "policyreporter.labels" . | nindent 4 }} 13 | type: Opaque 14 | data: 15 | config.yaml: {{ tpl (.Files.Get "configs/email-reports.tmpl") . | b64enc }} 16 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/config-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if not .Values.existingTargetConfig.enabled }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "policyreporter.fullname" . }}-config 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | {{- if .Values.annotations }} 8 | annotations: 9 | {{- toYaml .Values.annotations | nindent 4 }} 10 | {{- end }} 11 | labels: 12 | {{- include "policyreporter.labels" . | nindent 4 }} 13 | type: Opaque 14 | data: 15 | config.yaml: {{ tpl (.Files.Get "configs/core.tmpl") . | b64enc }} 16 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/extra-manifests.yaml: -------------------------------------------------------------------------------- 1 | {{ range .Values.extraManifests }} 2 | --- 3 | {{ tpl . $ }} 4 | {{ end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/monitoring/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Create a default fully qualified app name. 3 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 4 | If release name contains chart name it will be used as a full name. 5 | */}} 6 | {{- define "monitoring.fullname" -}} 7 | {{ template "policyreporter.fullname" . }}-monitoring 8 | {{- end }} 9 | 10 | {{- define "monitoring.name" -}} 11 | {{ template "policyreporter.name" . }}-monitoring 12 | {{- end }} 13 | 14 | 15 | {{/* 16 | Create chart name and version as used by the chart label. 17 | */}} 18 | {{- define "monitoring.chart" -}} 19 | {{ template "policyreporter.chart" . }} 20 | {{- end }} 21 | 22 | {{/* 23 | Common labels 24 | */}} 25 | {{- define "monitoring.labels" -}} 26 | {{ include "monitoring.selectorLabels" . }} 27 | {{- if .Chart.AppVersion }} 28 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 29 | {{- end }} 30 | app.kubernetes.io/component: monitoring 31 | app.kubernetes.io/part-of: kyverno 32 | {{- if not .Values.static }} 33 | app.kubernetes.io/managed-by: {{ .Release.Service }} 34 | helm.sh/chart: {{ include "monitoring.chart" . }} 35 | {{- end }} 36 | {{- with .Values.global.labels }} 37 | {{ toYaml . }} 38 | {{- end -}} 39 | {{- end }} 40 | 41 | {{/* 42 | Selector labels 43 | */}} 44 | {{- define "monitoring.selectorLabels" -}} 45 | app.kubernetes.io/name: {{ include "monitoring.name" . }} 46 | app.kubernetes.io/instance: {{ .Release.Name }} 47 | {{- end }} 48 | 49 | {{/* Get the namespace name. */}} 50 | {{- define "monitoring.smNamespace" -}} 51 | {{- if .Values.monitoring.serviceMonitor.namespace -}} 52 | {{- .Values.monitoring.serviceMonitor.namespace -}} 53 | {{- else if .Values.namespaceOverride -}} 54 | {{- .Values.namespaceOverride -}} 55 | {{- else -}} 56 | {{- .Release.Namespace -}} 57 | {{- end }} 58 | {{- end }} 59 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/monitoring/auth-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.monitoring.enabled }} 2 | {{- if and .Values.basicAuth.username .Values.basicAuth.password }} 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: {{ include "monitoring.fullname" . }}-auth 7 | namespace: {{ include "monitoring.smNamespace" . }} 8 | {{- if .Values.monitoring.annotations }} 9 | annotations: 10 | {{- toYaml .Values.monitoring.annotations | nindent 4 }} 11 | {{- end }} 12 | labels: 13 | {{- include "monitoring.labels" . | nindent 4 }} 14 | type: Opaque 15 | data: 16 | username: {{ .Values.basicAuth.username | b64enc }} 17 | password: {{ .Values.basicAuth.password | b64enc }} 18 | {{- end }} 19 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/monitoring/clusterpolicy-details.grafanadashboard.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.monitoring.enabled .Values.monitoring.grafana.dashboards.enabled .Values.monitoring.grafana.dashboards.enable.clusterPolicyReportDetails .Values.monitoring.grafana.grafanaDashboard.enabled }} 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaDashboard 4 | metadata: 5 | labels: 6 | {{ .Values.monitoring.grafana.dashboards.label }}: {{ .Values.monitoring.grafana.dashboards.value | quote }} 7 | {{- include "monitoring.labels" . | nindent 4 }} 8 | name: {{ include "monitoring.fullname" . }}-clusterpolicy-details-dashboard 9 | namespace: {{ include "grafana.namespace" . }} 10 | spec: 11 | allowCrossNamespaceImport: {{ .Values.monitoring.grafana.grafanaDashboard.allowCrossNamespaceImport }} 12 | folder: {{ .Values.monitoring.grafana.grafanaDashboard.folder }} 13 | instanceSelector: 14 | matchLabels: 15 | {{- toYaml .Values.monitoring.grafana.grafanaDashboard.matchLabels | nindent 6 }} 16 | configMapRef: 17 | name: {{ include "monitoring.fullname" . }}-clusterpolicy-details-dashboard 18 | key: cluster-policy-reporter-details-dashboard.json 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/monitoring/overview.grafanadashboard.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.monitoring.enabled .Values.monitoring.grafana.dashboards.enabled .Values.monitoring.grafana.dashboards.enable.overview .Values.monitoring.grafana.grafanaDashboard.enabled }} 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaDashboard 4 | metadata: 5 | labels: 6 | {{ .Values.monitoring.grafana.dashboards.label }}: {{ .Values.monitoring.grafana.dashboards.value | quote }} 7 | {{- include "monitoring.labels" . | nindent 4 }} 8 | name: {{ include "monitoring.fullname" . }}-overview-dashboard 9 | namespace: {{ include "grafana.namespace" . }} 10 | spec: 11 | allowCrossNamespaceImport: {{ .Values.monitoring.grafana.grafanaDashboard.allowCrossNamespaceImport }} 12 | folder: {{ .Values.monitoring.grafana.grafanaDashboard.folder }} 13 | instanceSelector: 14 | matchLabels: 15 | {{- toYaml .Values.monitoring.grafana.grafanaDashboard.matchLabels | nindent 6 }} 16 | configMapRef: 17 | name: {{ include "monitoring.fullname" . }}-overview-dashboard 18 | key: policy-reporter-dashboard.json 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/monitoring/policy-details.grafanadashboard.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.monitoring.enabled .Values.monitoring.grafana.dashboards.enabled .Values.monitoring.grafana.dashboards.enable.policyReportDetails .Values.monitoring.grafana.grafanaDashboard.enabled }} 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaDashboard 4 | metadata: 5 | labels: 6 | {{ .Values.monitoring.grafana.dashboards.label }}: {{ .Values.monitoring.grafana.dashboards.value | quote }} 7 | {{- include "monitoring.labels" . | nindent 4 }} 8 | name: {{ include "monitoring.fullname" . }}-policy-details-dashboard 9 | namespace: {{ include "grafana.namespace" . }} 10 | spec: 11 | allowCrossNamespaceImport: {{ .Values.monitoring.grafana.grafanaDashboard.allowCrossNamespaceImport }} 12 | folder: {{ .Values.monitoring.grafana.grafanaDashboard.folder }} 13 | instanceSelector: 14 | matchLabels: 15 | {{- toYaml .Values.monitoring.grafana.grafanaDashboard.matchLabels | nindent 6 }} 16 | configMapRef: 17 | name: {{ include "monitoring.fullname" . }}-policy-details-dashboard 18 | key: policy-reporter-details-dashboard.json 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/monitoring/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.monitoring.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: {{ include "monitoring.fullname" . }} 6 | namespace: {{ include "monitoring.smNamespace" . }} 7 | {{- if .Values.monitoring.annotations }} 8 | annotations: 9 | {{- toYaml .Values.monitoring.annotations | nindent 4 }} 10 | {{- end }} 11 | labels: 12 | {{- include "monitoring.labels" . | nindent 4 }} 13 | {{- with .Values.monitoring.serviceMonitor.labels }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | selector: 18 | matchLabels: 19 | {{- include "policyreporter.selectorLabels" . | nindent 8 }} 20 | {{- with .Values.monitoring.serviceMonitor.namespaceSelector }} 21 | namespaceSelector: 22 | {{- toYaml . | nindent 4 }} 23 | {{- end }} 24 | endpoints: 25 | - port: http 26 | {{- if and .Values.basicAuth.username .Values.basicAuth.password }} 27 | basicAuth: 28 | password: 29 | name: {{ include "monitoring.fullname" . }}-auth 30 | key: password 31 | username: 32 | name: {{ include "monitoring.fullname" . }}-auth 33 | key: username 34 | {{- else if .Values.basicAuth.secretRef }} 35 | basicAuth: 36 | password: 37 | name: {{ .Values.basicAuth.secretRef }} 38 | key: password 39 | username: 40 | name: {{ .Values.basicAuth.secretRef }} 41 | key: username 42 | {{- end }} 43 | honorLabels: {{ .Values.monitoring.serviceMonitor.honorLabels }} 44 | {{- if .Values.monitoring.serviceMonitor.scrapeTimeout }} 45 | scrapeTimeout: {{ .Values.monitoring.serviceMonitor.scrapeTimeout }} 46 | {{- end }} 47 | {{- if .Values.monitoring.serviceMonitor.interval }} 48 | interval: {{ .Values.monitoring.serviceMonitor.interval }} 49 | {{- end }} 50 | {{- with .Values.monitoring.serviceMonitor.relabelings }} 51 | relabelings: 52 | {{- toYaml . | nindent 4 }} 53 | {{- end }} 54 | {{- with .Values.monitoring.serviceMonitor.metricRelabelings }} 55 | metricRelabelings: 56 | {{- toYaml . | nindent 4 }} 57 | {{- end }} 58 | {{- end }} 59 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.networkPolicy.enabled }} 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | {{- if .Values.annotations }} 6 | annotations: 7 | {{- toYaml .Values.annotations | nindent 4 }} 8 | {{- end }} 9 | labels: {{ include "policyreporter.labels" . | nindent 4 }} 10 | name: {{ include "policyreporter.fullname" . }} 11 | namespace: {{ include "policyreporter.namespace" . }} 12 | spec: 13 | podSelector: 14 | matchLabels: {{- include "policyreporter.selectorLabels" . | nindent 6 }} 15 | policyTypes: 16 | - Ingress 17 | - Egress 18 | ingress: 19 | {{- if .Values.ui.enabled }} 20 | - from: 21 | - podSelector: 22 | matchLabels: {{- include "ui.selectorLabels" . | nindent 10 }} 23 | ports: 24 | - protocol: TCP 25 | port: {{ .Values.ui.service.port }} 26 | {{- end }} 27 | {{- if .Values.plugin.trivy.enabled }} 28 | - from: 29 | - podSelector: 30 | matchLabels: {{- include "trivy-plugin.selectorLabels" . | nindent 10 }} 31 | ports: 32 | - protocol: TCP 33 | port: {{ .Values.plugin.trivy.service.port }} 34 | {{- end }} 35 | {{- with .Values.networkPolicy.ingress }} 36 | {{- toYaml . | nindent 2 }} 37 | {{- end }} 38 | {{- with .Values.networkPolicy.egress }} 39 | egress: 40 | {{- toYaml . | nindent 2 }} 41 | {{- end }} 42 | {{- end }} 43 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if .Values.plugin.kyverno.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 8 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 9 | name: {{ include "kyverno-plugin.fullname" . }} 10 | rules: 11 | - apiGroups: 12 | - '*' 13 | resources: 14 | - policies 15 | - policies/status 16 | - clusterpolicies 17 | - clusterpolicies/status 18 | verbs: 19 | - get 20 | - list 21 | {{- if .Values.plugin.kyverno.blockReports.enabled }} 22 | - apiGroups: 23 | - "" 24 | resources: 25 | - events 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | - apiGroups: 31 | - '*' 32 | resources: 33 | - policyreports 34 | - policyreports/status 35 | - clusterpolicyreports 36 | - clusterpolicyreports/status 37 | verbs: 38 | - get 39 | - list 40 | - create 41 | - update 42 | - delete 43 | {{- end }} 44 | {{- end }} 45 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if and .Values.plugin.kyverno.serviceAccount.create .Values.plugin.kyverno.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRoleBinding 5 | metadata: 6 | name: {{ include "kyverno-plugin.fullname" . }} 7 | labels: 8 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 9 | roleRef: 10 | kind: ClusterRole 11 | name: {{ include "kyverno-plugin.fullname" . }} 12 | apiGroup: rbac.authorization.k8s.io 13 | subjects: 14 | - kind: "ServiceAccount" 15 | name: {{ include "kyverno-plugin.serviceAccountName" . }} 16 | namespace: {{ include "policyreporter.namespace" . }} 17 | {{- end }} 18 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/config-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "kyverno-plugin.fullname" . }}-config 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 9 | type: Opaque 10 | data: 11 | config.yaml: {{ tpl (.Files.Get "configs/kyverno-plugin.tmpl") . | b64enc }} 12 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if .Values.plugin.kyverno.networkPolicy.enabled }} 3 | apiVersion: networking.k8s.io/v1 4 | kind: NetworkPolicy 5 | metadata: 6 | labels: {{- include "kyverno-plugin.labels" . | nindent 4 }} 7 | name: {{ include "kyverno-plugin.fullname" . }} 8 | namespace: {{ include "policyreporter.namespace" . }} 9 | spec: 10 | podSelector: 11 | matchLabels: {{- include "kyverno-plugin.selectorLabels" . | nindent 6 }} 12 | policyTypes: 13 | - Ingress 14 | - Egress 15 | {{- with .Values.plugin.kyverno.networkPolicy.ingress }} 16 | ingress: 17 | {{- toYaml . | nindent 2 }} 18 | {{- end }} 19 | {{- with .Values.plugin.kyverno.networkPolicy.egress }} 20 | egress: 21 | {{- toYaml . | nindent 2 }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/poddisruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if (gt (int .Values.plugin.kyverno.replicaCount) 1) }} 3 | {{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }} 4 | apiVersion: policy/v1 5 | {{- else }} 6 | apiVersion: policy/v1beta1 7 | {{- end }} 8 | kind: PodDisruptionBudget 9 | metadata: 10 | name: {{ include "kyverno-plugin.fullname" . }} 11 | labels: 12 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 13 | spec: 14 | {{- include "kyverno-plugin.podDisruptionBudget" . | indent 2 }} 15 | selector: 16 | matchLabels: 17 | {{- include "kyverno-plugin.selectorLabels" . | nindent 6 }} 18 | {{- end }} 19 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if and (and .Values.plugin.kyverno.serviceAccount.create .Values.plugin.kyverno.rbac.enabled) (and .Values.plugin.kyverno.blockReports.enabled (gt (int .Values.plugin.kyverno.replicaCount) 1)) -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: Role 5 | metadata: 6 | labels: 7 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 8 | name: {{ include "kyverno-plugin.fullname" . }}-leaderelection 9 | namespace: {{ include "policyreporter.namespace" . }} 10 | rules: 11 | - apiGroups: 12 | - coordination.k8s.io 13 | resources: 14 | - leases 15 | verbs: 16 | - create 17 | - delete 18 | - get 19 | - patch 20 | - update 21 | {{- end }} 22 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if and (and .Values.plugin.kyverno.serviceAccount.create .Values.plugin.kyverno.rbac.enabled) (and .Values.plugin.kyverno.blockReports.enabled (gt (int .Values.plugin.kyverno.replicaCount) 1)) -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: RoleBinding 5 | metadata: 6 | name: {{ include "kyverno-plugin.fullname" . }}-leaderelection 7 | namespace: {{ include "policyreporter.namespace" . }} 8 | labels: 9 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 10 | roleRef: 11 | kind: Role 12 | name: {{ include "kyverno-plugin.fullname" . }}-leaderelection 13 | apiGroup: rbac.authorization.k8s.io 14 | subjects: 15 | - kind: "ServiceAccount" 16 | name: {{ include "kyverno-plugin.serviceAccountName" . }} 17 | namespace: {{ include "policyreporter.namespace" . }} 18 | {{- end }} 19 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/secret-role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if and .Values.plugin.kyverno.serviceAccount.create .Values.plugin.kyverno.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: Role 5 | metadata: 6 | labels: 7 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 8 | name: {{ include "kyverno-plugin.fullname" . }}-secret-reader 9 | namespace: {{ include "policyreporter.namespace" . }} 10 | rules: 11 | - apiGroups: [''] 12 | resources: 13 | - secrets 14 | verbs: 15 | - get 16 | {{- end }} 17 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/secret-rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if and .Values.plugin.kyverno.serviceAccount.create .Values.plugin.kyverno.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: RoleBinding 5 | metadata: 6 | name: {{ include "kyverno-plugin.fullname" . }}-secret-reader 7 | namespace: {{ include "policyreporter.namespace" . }} 8 | labels: 9 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 10 | roleRef: 11 | kind: Role 12 | name: {{ include "kyverno-plugin.fullname" . }}-secret-reader 13 | apiGroup: rbac.authorization.k8s.io 14 | subjects: 15 | - kind: "ServiceAccount" 16 | name: {{ include "kyverno-plugin.serviceAccountName" . }} 17 | namespace: {{ include "policyreporter.namespace" . }} 18 | {{- end }} 19 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "kyverno-plugin.fullname" . }} 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 9 | {{- with .Values.plugin.kyverno.service.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- with .Values.plugin.kyverno.service.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | type: {{ .Values.plugin.kyverno.service.type }} 18 | ports: 19 | - port: {{ .Values.plugin.kyverno.service.port }} 20 | targetPort: http 21 | protocol: TCP 22 | name: http 23 | selector: 24 | {{- include "kyverno-plugin.selectorLabels" . | nindent 4 }} 25 | {{- end }} 26 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/kyverno/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.kyverno.enabled -}} 2 | {{- if .Values.plugin.kyverno.serviceAccount.create -}} 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ include "kyverno-plugin.serviceAccountName" . }} 7 | namespace: {{ include "policyreporter.namespace" . }} 8 | labels: 9 | {{- include "kyverno-plugin.labels" . | nindent 4 }} 10 | {{- with .Values.plugin.kyverno.serviceAccount.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | automountServiceAccountToken: {{ .Values.plugin.kyverno.serviceAccount.automount }} 15 | {{- end }} 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/trivy/config-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.trivy.enabled -}} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "trivy-plugin.fullname" . }}-config 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "trivy-plugin.labels" . | nindent 4 }} 9 | type: Opaque 10 | data: 11 | config.yaml: {{ tpl (.Files.Get "configs/trivy-plugin.tmpl") . | b64enc }} 12 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/trivy/networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.trivy.enabled -}} 2 | {{- if .Values.plugin.trivy.networkPolicy.enabled }} 3 | apiVersion: networking.k8s.io/v1 4 | kind: NetworkPolicy 5 | metadata: 6 | labels: {{- include "trivy-plugin.labels" . | nindent 4 }} 7 | name: {{ include "trivy-plugin.fullname" . }} 8 | namespace: {{ include "policyreporter.namespace" . }} 9 | spec: 10 | podSelector: 11 | matchLabels: {{- include "trivy-plugin.selectorLabels" . | nindent 6 }} 12 | policyTypes: 13 | - Ingress 14 | - Egress 15 | {{- with .Values.plugin.trivy.networkPolicy.ingress }} 16 | ingress: 17 | {{- toYaml . | nindent 2 }} 18 | {{- end }} 19 | egress: 20 | - to: 21 | - podSelector: 22 | matchLabels: 23 | {{- include "policyreporter.selectorLabels" . | nindent 10 }} 24 | ports: 25 | - protocol: TCP 26 | port: {{ .Values.service.port }} 27 | {{- with .Values.plugin.trivy.networkPolicy.egress }} 28 | {{- toYaml . | nindent 2 }} 29 | {{- end }} 30 | {{- end }} 31 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/trivy/poddisruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.trivy.enabled -}} 2 | {{- if (gt (int .Values.plugin.trivy.replicaCount) 1) }} 3 | {{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }} 4 | apiVersion: policy/v1 5 | {{- else }} 6 | apiVersion: policy/v1beta1 7 | {{- end }} 8 | kind: PodDisruptionBudget 9 | metadata: 10 | name: {{ include "trivy-plugin.fullname" . }} 11 | namespace: {{ include "policyreporter.namespace" . }} 12 | labels: 13 | {{- include "trivy-plugin.labels" . | nindent 4 }} 14 | spec: 15 | {{- include "trivy-plugin.podDisruptionBudget" . | indent 2 }} 16 | selector: 17 | matchLabels: 18 | {{- include "trivy-plugin.selectorLabels" . | nindent 6 }} 19 | {{- end }} 20 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/trivy/secret-role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.trivy.enabled -}} 2 | {{- if and .Values.plugin.trivy.serviceAccount.create .Values.plugin.trivy.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: Role 5 | metadata: 6 | labels: 7 | {{- include "trivy-plugin.labels" . | nindent 4 }} 8 | name: {{ include "trivy-plugin.fullname" . }}-secret-reader 9 | namespace: {{ include "policyreporter.namespace" . }} 10 | rules: 11 | - apiGroups: [''] 12 | resources: 13 | - secrets 14 | verbs: 15 | - get 16 | {{- end }} 17 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/trivy/secret-rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.trivy.enabled -}} 2 | {{- if and .Values.plugin.trivy.serviceAccount.create .Values.plugin.trivy.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: RoleBinding 5 | metadata: 6 | name: {{ include "trivy-plugin.fullname" . }}-secret-reader 7 | namespace: {{ include "policyreporter.namespace" . }} 8 | labels: 9 | {{- include "trivy-plugin.labels" . | nindent 4 }} 10 | roleRef: 11 | kind: Role 12 | name: {{ include "trivy-plugin.fullname" . }}-secret-reader 13 | apiGroup: rbac.authorization.k8s.io 14 | subjects: 15 | - kind: "ServiceAccount" 16 | name: {{ include "trivy-plugin.serviceAccountName" . }} 17 | namespace: {{ include "policyreporter.namespace" . }} 18 | {{- end }} 19 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/trivy/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.trivy.enabled -}} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "trivy-plugin.fullname" . }} 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "trivy-plugin.labels" . | nindent 4 }} 9 | {{- with .Values.plugin.trivy.service.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- with .Values.plugin.trivy.service.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | type: {{ .Values.plugin.trivy.service.type }} 18 | ports: 19 | - port: {{ .Values.plugin.trivy.service.port }} 20 | targetPort: http 21 | protocol: TCP 22 | name: http 23 | selector: 24 | {{- include "trivy-plugin.selectorLabels" . | nindent 4 }} 25 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/plugins/trivy/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.plugin.trivy.enabled -}} 2 | {{- if .Values.plugin.trivy.serviceAccount.create -}} 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ include "trivy-plugin.serviceAccountName" . }} 7 | namespace: {{ include "policyreporter.namespace" . }} 8 | labels: 9 | {{- include "trivy-plugin.labels" . | nindent 4 }} 10 | {{- with .Values.plugin.trivy.serviceAccount.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | automountServiceAccountToken: {{ .Values.plugin.trivy.serviceAccount.automount }} 15 | {{- end }} 16 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/poddisruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | {{- if (gt (int .Values.replicaCount) 1) }} 2 | {{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }} 3 | apiVersion: policy/v1 4 | {{- else }} 5 | apiVersion: policy/v1beta1 6 | {{- end }} 7 | kind: PodDisruptionBudget 8 | metadata: 9 | name: {{ template "policyreporter.fullname" . }} 10 | namespace: {{ include "policyreporter.namespace" . }} 11 | labels: 12 | {{- include "policyreporter.labels" . | nindent 4 }} 13 | {{- if .Values.annotations }} 14 | annotations: 15 | {{- toYaml .Values.annotations | nindent 4 }} 16 | {{- end }} 17 | spec: 18 | {{- include "policyreporter.podDisruptionBudget" . | indent 2 }} 19 | selector: 20 | matchLabels: 21 | {{- include "policyreporter.selectorLabels" . | nindent 6 }} 22 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/role.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.rbac.enabled (gt (int .Values.replicaCount) 1) -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | {{- if .Values.annotations }} 6 | annotations: 7 | {{- toYaml .Values.annotations | nindent 4 }} 8 | {{- end }} 9 | labels: 10 | {{- include "policyreporter.labels" . | nindent 4 }} 11 | name: {{ include "policyreporter.fullname" . }}-leaderelection 12 | namespace: {{ include "policyreporter.namespace" . }} 13 | rules: 14 | - apiGroups: 15 | - coordination.k8s.io 16 | resources: 17 | - leases 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - patch 23 | - update 24 | {{- end -}} 25 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.rbac.enabled (gt (int .Values.replicaCount) 1) -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "policyreporter.fullname" . }}-leaderelection 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | {{- if .Values.annotations }} 8 | annotations: 9 | {{- toYaml .Values.annotations | nindent 4 }} 10 | {{- end }} 11 | labels: 12 | {{- include "policyreporter.labels" . | nindent 4 }} 13 | roleRef: 14 | kind: Role 15 | name: {{ include "policyreporter.fullname" . }}-leaderelection 16 | apiGroup: rbac.authorization.k8s.io 17 | subjects: 18 | - kind: "ServiceAccount" 19 | name: {{ include "policyreporter.serviceAccountName" . }} 20 | namespace: {{ include "policyreporter.namespace" . }} 21 | {{- end -}} 22 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/secret-role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.enabled -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | {{- if .Values.annotations }} 6 | annotations: 7 | {{- toYaml .Values.annotations | nindent 4 }} 8 | {{- end }} 9 | labels: 10 | {{- include "policyreporter.labels" . | nindent 4 }} 11 | name: {{ include "policyreporter.fullname" . }}-secret-reader 12 | namespace: {{ include "policyreporter.namespace" . }} 13 | rules: 14 | - apiGroups: [''] 15 | resources: 16 | - secrets 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | {{- end -}} 22 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/secret-rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.enabled -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "policyreporter.fullname" . }}-secret-reader 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | {{- if .Values.annotations }} 8 | annotations: 9 | {{- toYaml .Values.annotations | nindent 4 }} 10 | {{- end }} 11 | labels: 12 | {{- include "policyreporter.labels" . | nindent 4 }} 13 | roleRef: 14 | kind: Role 15 | name: {{ include "policyreporter.fullname" . }}-secret-reader 16 | apiGroup: rbac.authorization.k8s.io 17 | subjects: 18 | - kind: "ServiceAccount" 19 | name: {{ include "policyreporter.serviceAccountName" . }} 20 | namespace: {{ include "policyreporter.namespace" . }} 21 | {{- end -}} 22 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.service.enabled -}} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "policyreporter.fullname" . }} 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "policyreporter.labels" . | nindent 4 }} 9 | {{- with .Values.service.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- if or .Values.annotations .Values.service.annotations }} 13 | annotations: 14 | {{- with .Values.annotations }} 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | {{- with .Values.service.annotations }} 18 | {{- toYaml . | nindent 4 }} 19 | {{- end }} 20 | {{- end }} 21 | spec: 22 | type: {{ .Values.service.type }} 23 | ports: 24 | - port: {{ .Values.service.port }} 25 | targetPort: {{ .Values.port.name }} 26 | protocol: TCP 27 | name: http 28 | selector: 29 | {{- include "policyreporter.selectorLabels" . | nindent 4 }} 30 | {{- end }} 31 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "policyreporter.serviceAccountName" . }} 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "policyreporter.labels" . | nindent 4 }} 9 | {{- if or .Values.annotations .Values.serviceAccount.annotations }} 10 | annotations: 11 | {{- with .Values.annotations }} 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | {{- with .Values.serviceAccount.annotations }} 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | {{- end }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/ui/config-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "ui.fullname" . }}-config 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "ui.labels" . | nindent 4 }} 9 | type: Opaque 10 | data: 11 | config.yaml: {{ tpl (.Files.Get "configs/ui.tmpl") . | b64enc }} 12 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/ui/networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | {{- if .Values.ui.networkPolicy.enabled }} 3 | apiVersion: networking.k8s.io/v1 4 | kind: NetworkPolicy 5 | metadata: 6 | labels: {{- include "ui.labels" . | nindent 4 }} 7 | name: {{ include "ui.fullname" . }} 8 | namespace: {{ include "policyreporter.namespace" . }} 9 | spec: 10 | podSelector: 11 | matchLabels: {{- include "ui.selectorLabels" . | nindent 6 }} 12 | policyTypes: 13 | - Ingress 14 | - Egress 15 | ingress: 16 | - from: 17 | ports: 18 | - protocol: TCP 19 | port: {{ .Values.ui.service.port }} 20 | {{- with .Values.ui.networkPolicy.ingress }} 21 | {{- toYaml . | nindent 2 }} 22 | {{- end }} 23 | egress: 24 | - to: 25 | - podSelector: 26 | matchLabels: 27 | {{- include "policyreporter.selectorLabels" . | nindent 10 }} 28 | ports: 29 | - protocol: TCP 30 | port: {{ .Values.service.port }} 31 | {{- if or .Values.plugin.kyverno.enabled }} 32 | - to: 33 | - podSelector: 34 | matchLabels: 35 | {{- include "kyverno-plugin.selectorLabels" . | nindent 10 }} 36 | ports: 37 | - protocol: TCP 38 | port: {{ .Values.plugin.kyverno.service.port }} 39 | {{- end }} 40 | {{- if or .Values.plugin.trivy.enabled }} 41 | - to: 42 | - podSelector: 43 | matchLabels: 44 | {{- include "trivy-plugin.selectorLabels" . | nindent 10 }} 45 | ports: 46 | - protocol: TCP 47 | port: {{ .Values.plugin.trivy.service.port }} 48 | {{- end }} 49 | {{- with .Values.networkPolicy.egress }} 50 | {{- toYaml . | nindent 2 }} 51 | {{- end }} 52 | {{- end }} 53 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/ui/poddisruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | {{- if (gt (int .Values.ui.replicaCount) 1) }} 3 | {{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }} 4 | apiVersion: policy/v1 5 | {{- else }} 6 | apiVersion: policy/v1beta1 7 | {{- end }} 8 | kind: PodDisruptionBudget 9 | metadata: 10 | name: {{ include "ui.fullname" . }} 11 | namespace: {{ include "policyreporter.namespace" . }} 12 | labels: 13 | {{- include "ui.labels" . | nindent 4 }} 14 | spec: 15 | {{- include "ui.podDisruptionBudget" . | indent 2 }} 16 | selector: 17 | matchLabels: 18 | {{- include "ui.selectorLabels" . | nindent 6 }} 19 | {{- end }} 20 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/ui/secret-role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | {{- if and .Values.ui.serviceAccount.create .Values.ui.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: Role 5 | metadata: 6 | labels: 7 | {{- include "ui.labels" . | nindent 4 }} 8 | name: {{ include "ui.fullname" . }}-secret-reader 9 | namespace: {{ include "policyreporter.namespace" . }} 10 | rules: 11 | - apiGroups: [''] 12 | resources: 13 | - secrets 14 | verbs: 15 | - get 16 | {{- end -}} 17 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/ui/secret-rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | {{- if and .Values.ui.serviceAccount.create .Values.rbac.enabled -}} 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: RoleBinding 5 | metadata: 6 | name: {{ include "ui.fullname" . }}-secret-reader 7 | namespace: {{ include "policyreporter.namespace" . }} 8 | labels: 9 | {{- include "ui.labels" . | nindent 4 }} 10 | roleRef: 11 | kind: Role 12 | name: {{ include "ui.fullname" . }}-secret-reader 13 | apiGroup: rbac.authorization.k8s.io 14 | subjects: 15 | - kind: "ServiceAccount" 16 | name: {{ include "ui.serviceAccountName" . }} 17 | namespace: {{ include "policyreporter.namespace" . }} 18 | {{- end -}} 19 | {{- end }} -------------------------------------------------------------------------------- /charts/policy-reporter/templates/ui/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "ui.fullname" . }} 6 | namespace: {{ include "policyreporter.namespace" . }} 7 | labels: 8 | {{- include "ui.labels" . | nindent 4 }} 9 | {{- with .Values.ui.service.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- with .Values.ui.service.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | type: {{ .Values.ui.service.type }} 18 | ports: 19 | - port: {{ .Values.ui.service.port }} 20 | targetPort: http 21 | protocol: TCP 22 | name: http 23 | {{- if .Values.ui.service.additionalPorts }} 24 | {{- toYaml .Values.ui.service.additionalPorts | nindent 4 }} 25 | {{- end }} 26 | selector: 27 | {{- include "ui.selectorLabels" . | nindent 4 }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /charts/policy-reporter/templates/ui/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ui.enabled -}} 2 | {{- if .Values.ui.serviceAccount.create -}} 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ include "ui.serviceAccountName" . }} 7 | namespace: {{ include "policyreporter.namespace" . }} 8 | labels: 9 | {{- include "ui.labels" . | nindent 4 }} 10 | {{- with .Values.ui.serviceAccount.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | automountServiceAccountToken: {{ .Values.ui.serviceAccount.automount }} 15 | {{- end }} 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // NewCLI creates a new instance of the root CLI 8 | func NewCLI(version string) *cobra.Command { 9 | rootCmd := &cobra.Command{ 10 | Use: "policyreporter", 11 | Short: "Generates PolicyReport Metrics and Send Results to different targets", 12 | Long: `Generates Prometheus Metrics from PolicyReports, ClusterPolicyReports and PolicyReportResults. 13 | Sends notifications to different targets like Grafana's Loki.`, 14 | } 15 | 16 | rootCmd.AddCommand(newVersionCMD(version)) 17 | rootCmd.AddCommand(newRunCMD(version)) 18 | rootCmd.AddCommand(newSendCMD()) 19 | 20 | return rootCmd 21 | } 22 | -------------------------------------------------------------------------------- /cmd/send.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/kyverno/policy-reporter/cmd/send" 9 | ) 10 | 11 | func newSendCMD() *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: "send", 14 | Short: "Send different kinds of email reports", 15 | } 16 | 17 | // For local usage 18 | cmd.PersistentFlags().StringP("kubeconfig", "k", "", "absolute path to the kubeconfig file") 19 | cmd.PersistentFlags().StringP("config", "c", "", "target configuration file") 20 | cmd.PersistentFlags().StringP("template-dir", "t", "./templates", "template directory for email reports") 21 | cmd.AddCommand(send.NewSummaryCMD()) 22 | cmd.AddCommand(send.NewViolationsCMD()) 23 | 24 | flag.Parse() 25 | 26 | return cmd 27 | } 28 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func newVersionCMD(version string) *cobra.Command { 10 | return &cobra.Command{ 11 | Use: "version", 12 | Short: "Policy Reporter AppVersion", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | fmt.Println("AppVersion: " + version) 15 | }, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/CUSTOM_BOARDS.md: -------------------------------------------------------------------------------- 1 | # Policy Reporter UI - Custom Boards 2 | 3 | CustomBoards allows you to configure additional dashboards with a custom subset of sources and namespaces, selected via a list and/or label selector 4 | 5 | ## Example CustomBoard Config 6 | 7 | ![Custom Boards](https://github.com/kyverno/policy-reporter/blob/3.x/docs/images/custom-boards/list.png) 8 | 9 | ```yaml 10 | ui: 11 | enabled: true 12 | 13 | customBoards: 14 | - name: System 15 | namespaces: 16 | list: 17 | - kube-system 18 | - kyverno 19 | - policy-reporter 20 | ``` 21 | 22 | ### CustomBoard with NamespaceSelector 23 | 24 | ![Custom Boards](https://github.com/kyverno/policy-reporter/blob/3.x/docs/images/custom-boards/selector.png) 25 | 26 | ```yaml 27 | ui: 28 | enabled: true 29 | 30 | customBoards: 31 | - name: System 32 | namespaces: 33 | selector: 34 | group: system 35 | ``` 36 | 37 | ### CustomBoard with ClusterResources 38 | ![Custom Boards](https://github.com/kyverno/policy-reporter/blob/3.x/docs/images/custom-boards/cluster.png) 39 | 40 | ```yaml 41 | ui: 42 | enabled: true 43 | 44 | customBoards: 45 | - name: System 46 | clusterScope: 47 | enabled: true 48 | namespaces: 49 | selector: 50 | group: system 51 | ``` 52 | 53 | ### CustomBoard with Source List 54 | 55 | ![Custom Boards](https://github.com/kyverno/policy-reporter/blob/3.x/docs/images/custom-boards/source.png) 56 | 57 | ```yaml 58 | ui: 59 | enabled: true 60 | 61 | customBoards: 62 | - name: System 63 | clusterScope: 64 | enabled: true 65 | namespaces: 66 | selector: 67 | group: system 68 | sources: 69 | list: [kyverno] 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/SETUP.md: -------------------------------------------------------------------------------- 1 | # Setup Kyverno and Policy Reporter 2 | 3 | ## Install Kyverno + Kyverno PSS Policies 4 | 5 | Add Helm Repo 6 | 7 | ```bash 8 | helm repo add kyverno https://kyverno.github.io/kyverno/ 9 | helm repo update 10 | ``` 11 | 12 | Install Kyverno + PSS Policies 13 | 14 | ```bash 15 | helm upgrade --install kyverno kyverno/kyverno -n kyverno --create-namespace 16 | helm upgrade --install kyverno-policies kyverno/kyverno-policies -n kyverno --create-namespace --set podSecurityStandard=restricted 17 | ``` 18 | 19 | ## Installing Policy Reporter v3 Preview and Policy Reporter UI v2 + Kyverno Plugin 20 | 21 | ```bash 22 | helm repo add policy-reporter https://kyverno.github.io/policy-reporter 23 | helm repo update 24 | ``` 25 | 26 | Install the Policy Reporter Preview 27 | 28 | ```bash 29 | helm upgrade --install policy-reporter policy-reporter/policy-reporter --create-namespace -n policy-reporter --set ui.enabled=true --set plugin.kyverno.enabled=true 30 | ``` -------------------------------------------------------------------------------- /docs/images/cluster-policy-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/cluster-policy-details.png -------------------------------------------------------------------------------- /docs/images/custom-boards/cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/custom-boards/cluster.png -------------------------------------------------------------------------------- /docs/images/custom-boards/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/custom-boards/list.png -------------------------------------------------------------------------------- /docs/images/custom-boards/selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/custom-boards/selector.png -------------------------------------------------------------------------------- /docs/images/custom-boards/source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/custom-boards/source.png -------------------------------------------------------------------------------- /docs/images/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/discord.png -------------------------------------------------------------------------------- /docs/images/elasticsearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/elasticsearch.png -------------------------------------------------------------------------------- /docs/images/exceptions/exception-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/exceptions/exception-dialog.png -------------------------------------------------------------------------------- /docs/images/exceptions/resource-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/exceptions/resource-list.png -------------------------------------------------------------------------------- /docs/images/grafana-loki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/grafana-loki.png -------------------------------------------------------------------------------- /docs/images/ms-teams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/ms-teams.png -------------------------------------------------------------------------------- /docs/images/policy-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/policy-details.png -------------------------------------------------------------------------------- /docs/images/policy-reporter-ui-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/policy-reporter-ui-log.png -------------------------------------------------------------------------------- /docs/images/policy-reports-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/policy-reports-dashboard.png -------------------------------------------------------------------------------- /docs/images/prometheus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/prometheus.png -------------------------------------------------------------------------------- /docs/images/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/screen.png -------------------------------------------------------------------------------- /docs/images/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyverno/policy-reporter/48f70fd5faf983f9d17c60ae7c2f3d585669eb57/docs/images/slack.png -------------------------------------------------------------------------------- /examples/jira-target.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: policyreporter.io/v1alpha1 2 | kind: TargetConfig 3 | metadata: 4 | name: policy-reporter-jira 5 | spec: 6 | jira: 7 | # JIRA server URL 8 | host: "https://your-jira-instance.atlassian.net" 9 | # JIRA project key 10 | projectKey: "POL" 11 | # JIRA issue type, defaults to "Bug" if not specified 12 | issueType: "Bug" 13 | # JIRA authentication - either use username/password or username/apiToken 14 | username: "your-jira-username" 15 | # Use API token (recommended over password) 16 | apiToken: "your-jira-api-token" 17 | # Alternatively, use password (not recommended for production) 18 | # password: "your-jira-password" 19 | 20 | # Skip TLS verification (not recommended for production) 21 | skipTLS: false 22 | 23 | # Minimum severity to report to JIRA 24 | # Possible values: info, warning, error, critical 25 | minimumSeverity: "warning" 26 | 27 | # Filter results by namespace 28 | filter: 29 | namespaces: 30 | include: 31 | - "default" 32 | - "kube-system" 33 | 34 | # Filter by policy name (supports wildcards) 35 | policies: 36 | include: 37 | - "require-pod-probes" 38 | - "require-resources-*" 39 | 40 | # Custom fields to add to JIRA issues 41 | customFields: 42 | # Map custom fields to JIRA custom field IDs 43 | # customfield_10001: "Policy Violation" 44 | # customfield_10002: "Kubernetes" -------------------------------------------------------------------------------- /hack/controller-gen/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kyverno/kyverno/hack/controller-gen 2 | 3 | go 1.22.4 4 | 5 | require ( 6 | github.com/spf13/cobra v1.8.1 7 | k8s.io/apiextensions-apiserver v0.31.4 8 | sigs.k8s.io/controller-tools v0.16.5 9 | ) 10 | 11 | require ( 12 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 13 | github.com/go-logr/logr v1.4.2 // indirect 14 | github.com/gobuffalo/flect v1.0.3 // indirect 15 | github.com/gogo/protobuf v1.3.2 // indirect 16 | github.com/google/gofuzz v1.2.0 // indirect 17 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 18 | github.com/json-iterator/go v1.1.12 // indirect 19 | github.com/kr/text v0.2.0 // indirect 20 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 21 | github.com/modern-go/reflect2 v1.0.2 // indirect 22 | github.com/spf13/pflag v1.0.5 // indirect 23 | github.com/x448/float16 v0.8.4 // indirect 24 | golang.org/x/mod v0.22.0 // indirect 25 | golang.org/x/net v0.34.0 // indirect 26 | golang.org/x/sync v0.10.0 // indirect 27 | golang.org/x/text v0.21.0 // indirect 28 | golang.org/x/tools v0.29.0 // indirect 29 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 30 | gopkg.in/inf.v0 v0.9.1 // indirect 31 | gopkg.in/yaml.v2 v2.4.0 // indirect 32 | k8s.io/apimachinery v0.31.4 // indirect 33 | k8s.io/klog/v2 v2.130.1 // indirect 34 | k8s.io/utils v0.0.0-20240821151609-f90d01438635 // indirect 35 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 36 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /hack/controller-gen/markers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 7 | ) 8 | 9 | type OneOf struct { 10 | Value any 11 | } 12 | 13 | func (m OneOf) ApplyToSchema(schema *apiext.JSONSchemaProps) error { 14 | var props apiext.JSONSchemaProps 15 | if data, err := json.Marshal(m.Value); err != nil { 16 | return err 17 | } else if err := json.Unmarshal(data, &props); err != nil { 18 | return err 19 | } 20 | schema.OneOf = append(schema.OneOf, props) 21 | return nil 22 | } 23 | 24 | type Not struct { 25 | Value any 26 | } 27 | 28 | func (m Not) ApplyToSchema(schema *apiext.JSONSchemaProps) error { 29 | var props apiext.JSONSchemaProps 30 | if data, err := json.Marshal(m.Value); err != nil { 31 | return err 32 | } else if err := json.Unmarshal(data, &props); err != nil { 33 | return err 34 | } 35 | schema.Not = &props 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/kyverno/policy-reporter/cmd" 8 | ) 9 | 10 | var Version = "development" 11 | 12 | func main() { 13 | if err := cmd.NewCLI(Version).Execute(); err != nil { 14 | fmt.Fprintln(os.Stderr, err) 15 | os.Exit(1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /manifests/README.md: -------------------------------------------------------------------------------- 1 | # Installation Manifests for Policy Reporter 2 | 3 | You can use this manifests to install Policy Reporter without additional tools like Helm or Kustomize. The manifests are structured into different feature sets. 4 | 5 | All installations must take place in the `policy-reporter` namespace. As its the configured namespace for RBAC resources. 6 | 7 | ## Policy Reporter 8 | 9 | The `policy-reporter` folder is a basic installation for Policy Reporter without the UI or other components. It runs with the REST API and Metrics Endpoint enabled. 10 | 11 | ```bash 12 | kubectl apply -f https://raw.githubusercontent.com/kyverno/policy-reporter/main/manifests/policy-reporter/install.yaml 13 | ``` 14 | 15 | ## Policy Reporter UI 16 | 17 | The `policy-reporter-ui` folder installs Policy Reporter together with the Policy Reporter UI components and Metrics enabled. 18 | 19 | ```bash 20 | kubectl apply -f https://raw.githubusercontent.com/kyverno/policy-reporter/main/manifests/policy-reporter-ui/install.yaml 21 | ``` 22 | 23 | ## Policy Reporter UI + Kyverno Plugin 24 | 25 | The `policy-reporter-kyverno-ui` folder installs Policy Reporter together with the Policy Reporter UI, Kyverno Plugin components and Metrics enabled. 26 | 27 | ```bash 28 | kubectl apply -f https://raw.githubusercontent.com/kyverno/policy-reporter/main/manifests/policy-reporter-kyverno-ui/install.yaml 29 | ``` 30 | 31 | ## Policy Reporter UI + Kyverno Plugin in HA Mode 32 | 33 | The `policy-reporter-kyverno-ui-ha` installs the same compoments as `policy-reporter-kyverno-ui` but runs all components in HA mode (2 replicas) and creates additional resources for leader elections. 34 | 35 | ```bash 36 | kubectl apply -f https://raw.githubusercontent.com/kyverno/policy-reporter/main/manifests/policy-reporter-kyverno-ui-ha/install.yaml 37 | ``` 38 | -------------------------------------------------------------------------------- /pkg/api/healthz.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type HealthCheck = func() error 11 | 12 | // HealthzHandler for the Halthz REST API 13 | func HealthzHandler(checks []HealthCheck) gin.HandlerFunc { 14 | return func(ctx *gin.Context) { 15 | for _, c := range checks { 16 | if err := c(); err != nil { 17 | zap.L().Warn("health check failed", zap.Error(err)) 18 | ctx.AbortWithError(http.StatusServiceUnavailable, err) 19 | return 20 | } 21 | } 22 | 23 | ctx.JSON(http.StatusOK, gin.H{}) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/api/healthz_test.go: -------------------------------------------------------------------------------- 1 | package api_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/stretchr/testify/assert" 11 | 12 | "github.com/kyverno/policy-reporter/pkg/api" 13 | ) 14 | 15 | func TestHealthCheckSuccess(t *testing.T) { 16 | check := func() error { 17 | return nil 18 | } 19 | 20 | gin.SetMode(gin.ReleaseMode) 21 | 22 | server := api.NewServer(gin.New(), api.WithHealthChecks([]api.HealthCheck{check})) 23 | 24 | req, _ := http.NewRequest("GET", "/healthz", nil) 25 | w := httptest.NewRecorder() 26 | 27 | server.Serve(w, req) 28 | 29 | assert := assert.New(t) 30 | assert.Equal(http.StatusOK, w.Code) 31 | } 32 | 33 | func TestHealthCheckError(t *testing.T) { 34 | check := func() error { 35 | return nil 36 | } 37 | 38 | err := func() error { 39 | return errors.New("unhealthy") 40 | } 41 | 42 | gin.SetMode(gin.ReleaseMode) 43 | 44 | server := api.NewServer(gin.New(), api.WithHealthChecks([]api.HealthCheck{check, err})) 45 | 46 | req, _ := http.NewRequest("GET", "/healthz", nil) 47 | w := httptest.NewRecorder() 48 | 49 | server.Serve(w, req) 50 | 51 | assert := assert.New(t) 52 | assert.Equal(http.StatusServiceUnavailable, w.Code) 53 | } 54 | 55 | func TestReadyCheckSuccess(t *testing.T) { 56 | check := func() error { 57 | return nil 58 | } 59 | 60 | gin.SetMode(gin.ReleaseMode) 61 | 62 | server := api.NewServer(gin.New(), api.WithHealthChecks([]api.HealthCheck{check})) 63 | 64 | req, _ := http.NewRequest("GET", "/ready", nil) 65 | w := httptest.NewRecorder() 66 | 67 | server.Serve(w, req) 68 | 69 | assert := assert.New(t) 70 | assert.Equal(http.StatusOK, w.Code) 71 | } 72 | -------------------------------------------------------------------------------- /pkg/api/metrics.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/prometheus/client_golang/prometheus/promhttp" 6 | ) 7 | 8 | func MetricsHandler() gin.HandlerFunc { 9 | h := promhttp.Handler() 10 | 11 | return func(c *gin.Context) { 12 | h.ServeHTTP(c.Writer, c.Request) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/api/metrics_test.go: -------------------------------------------------------------------------------- 1 | package api_test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/kyverno/policy-reporter/pkg/api" 12 | ) 13 | 14 | func TestMetrics(t *testing.T) { 15 | gin.SetMode(gin.ReleaseMode) 16 | 17 | server := api.NewServer(gin.New(), api.WithMetrics()) 18 | 19 | req, _ := http.NewRequest("GET", "/metrics", nil) 20 | w := httptest.NewRecorder() 21 | 22 | server.Serve(w, req) 23 | 24 | assert := assert.New(t) 25 | assert.Equal(http.StatusOK, w.Code) 26 | } 27 | 28 | func TestMetricsWithBasicAuthError(t *testing.T) { 29 | gin.SetMode(gin.ReleaseMode) 30 | 31 | server := api.NewServer(gin.New(), api.WithBasicAuth(api.BasicAuth{ 32 | Username: "user", 33 | Password: "password", 34 | }), api.WithMetrics()) 35 | 36 | req, _ := http.NewRequest("GET", "/metrics", nil) 37 | w := httptest.NewRecorder() 38 | 39 | server.Serve(w, req) 40 | 41 | assert := assert.New(t) 42 | assert.Equal(http.StatusUnauthorized, w.Code) 43 | } 44 | 45 | func TestMetricsWithBasicAuthSuccess(t *testing.T) { 46 | gin.SetMode(gin.ReleaseMode) 47 | 48 | server := api.NewServer(gin.New(), api.WithBasicAuth(api.BasicAuth{ 49 | Username: "user", 50 | Password: "password", 51 | }), api.WithMetrics()) 52 | 53 | req, _ := http.NewRequest("GET", "/metrics", nil) 54 | req.SetBasicAuth("user", "password") 55 | w := httptest.NewRecorder() 56 | 57 | server.Serve(w, req) 58 | 59 | assert := assert.New(t) 60 | assert.Equal(http.StatusOK, w.Code) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/api/v1/model_test.go: -------------------------------------------------------------------------------- 1 | package v1_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | v1 "github.com/kyverno/policy-reporter/pkg/api/v1" 9 | "github.com/kyverno/policy-reporter/pkg/database" 10 | ) 11 | 12 | func TestMapping(t *testing.T) { 13 | t.Run("MapClusterStatusCounts", func(t *testing.T) { 14 | result := v1.MapClusterStatusCounts([]database.StatusCount{ 15 | {Source: "kyverno", Status: "pass", Count: 3}, 16 | {Source: "kyverno", Status: "fail", Count: 4}, 17 | }, []string{"pass", "fail"}) 18 | 19 | assert.Equal(t, 2, len(result)) 20 | assert.Contains(t, result, v1.StatusCount{Status: "pass", Count: 3}) 21 | assert.Contains(t, result, v1.StatusCount{Status: "fail", Count: 4}) 22 | }) 23 | 24 | t.Run("MapNamespaceStatusCounts", func(t *testing.T) { 25 | result := v1.MapNamespaceStatusCounts([]database.StatusCount{ 26 | {Source: "kyverno", Status: "pass", Count: 3, Namespace: "default"}, 27 | {Source: "kyverno", Status: "fail", Count: 4, Namespace: "default"}, 28 | {Source: "kyverno", Status: "pass", Count: 2, Namespace: "user"}, 29 | {Source: "kyverno", Status: "fail", Count: 2, Namespace: "user"}, 30 | }, []string{"pass", "fail"}) 31 | 32 | assert.Equal(t, 2, len(result)) 33 | 34 | assert.Contains(t, result, v1.NamespaceStatusCount{Status: "pass", Items: []v1.NamespaceCount{ 35 | {Namespace: "default", Count: 3, Status: "pass"}, 36 | {Namespace: "user", Count: 2, Status: "pass"}, 37 | }}) 38 | 39 | assert.Contains(t, result, v1.NamespaceStatusCount{Status: "fail", Items: []v1.NamespaceCount{ 40 | {Namespace: "default", Count: 4, Status: "fail"}, 41 | {Namespace: "user", Count: 2, Status: "fail"}, 42 | }}) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 4 | 5 | type Cache interface { 6 | RemoveReport(id string) 7 | AddReport(report v1alpha2.ReportInterface) 8 | GetResults(id string) []string 9 | Shared() bool 10 | Clear() 11 | } 12 | -------------------------------------------------------------------------------- /pkg/cache/memory_test.go: -------------------------------------------------------------------------------- 1 | package cache_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/kyverno/policy-reporter/pkg/cache" 8 | "github.com/kyverno/policy-reporter/pkg/fixtures" 9 | ) 10 | 11 | func TestInMemory(t *testing.T) { 12 | t.Run("add report", func(t *testing.T) { 13 | id := fixtures.DefaultPolicyReport.GetID() 14 | 15 | c := cache.NewInMermoryCache(time.Millisecond, time.Millisecond) 16 | 17 | c.AddReport(fixtures.DefaultPolicyReport) 18 | 19 | results := c.GetResults(id) 20 | if len(results) != len(fixtures.DefaultPolicyReport.Results) { 21 | t.Error("expected all results were cached") 22 | } 23 | 24 | c.AddReport(fixtures.MinPolicyReport) 25 | 26 | time.Sleep(3 * time.Millisecond) 27 | 28 | changed := c.GetResults(id) 29 | if len(changed) != len(fixtures.MinPolicyReport.Results) { 30 | t.Error("expected all old results were removed") 31 | } 32 | }) 33 | t.Run("remove report", func(t *testing.T) { 34 | id := fixtures.DefaultPolicyReport.GetID() 35 | 36 | c := cache.NewInMermoryCache(time.Millisecond, time.Millisecond) 37 | 38 | c.AddReport(fixtures.DefaultPolicyReport) 39 | 40 | c.RemoveReport(id) 41 | 42 | time.Sleep(3 * time.Millisecond) 43 | 44 | results := c.GetResults(id) 45 | if len(results) != 0 { 46 | t.Error("expected all results were removed") 47 | } 48 | }) 49 | t.Run("ceanup report", func(t *testing.T) { 50 | id := fixtures.DefaultPolicyReport.GetID() 51 | 52 | c := cache.NewInMermoryCache(time.Millisecond, time.Millisecond) 53 | 54 | c.AddReport(fixtures.DefaultPolicyReport) 55 | 56 | c.Clear() 57 | 58 | results := c.GetResults(id) 59 | if len(results) != 0 { 60 | t.Error("expected all results were cleaned up") 61 | } 62 | }) 63 | t.Run("shared cache", func(t *testing.T) { 64 | c := cache.NewInMermoryCache(time.Millisecond, time.Millisecond) 65 | if c.Shared() { 66 | t.Error("expected in memory cache is not shared") 67 | } 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/config/readinessprobe.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | ) 6 | 7 | type ReadinessProbe struct { 8 | config *Config 9 | 10 | ready chan bool 11 | running bool 12 | } 13 | 14 | func (r *ReadinessProbe) required() bool { 15 | if !r.config.REST.Enabled { 16 | return false 17 | } 18 | 19 | return r.config.LeaderElection.Enabled 20 | } 21 | 22 | func (r *ReadinessProbe) Ready() { 23 | if r.required() && !r.running { 24 | go func() { 25 | zap.L().Debug("readiness probe ready") 26 | r.ready <- true 27 | }() 28 | } 29 | } 30 | 31 | func (r *ReadinessProbe) Wait() { 32 | if r.required() && !r.running { 33 | r.running = <-r.ready 34 | zap.L().Debug("readiness probe finished") 35 | return 36 | } 37 | } 38 | 39 | func (r *ReadinessProbe) Close() { 40 | close(r.ready) 41 | } 42 | 43 | func (r *ReadinessProbe) Running() bool { 44 | return r.running 45 | } 46 | 47 | func NewReadinessProbe(config *Config) *ReadinessProbe { 48 | return &ReadinessProbe{ 49 | config: config, 50 | ready: make(chan bool), 51 | running: false, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/config/readinessprobe_test.go: -------------------------------------------------------------------------------- 1 | package config_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/config" 7 | ) 8 | 9 | func Test_ReadinessProbe(t *testing.T) { 10 | t.Run("immediate return without REST enabled", func(t *testing.T) { 11 | rdy := config.NewReadinessProbe( 12 | &config.Config{ 13 | REST: config.REST{Enabled: false}, 14 | LeaderElection: config.LeaderElection{Enabled: false}, 15 | }, 16 | ) 17 | 18 | rdy.Wait() 19 | }) 20 | 21 | t.Run("immediate return without LeaderElection enabled", func(t *testing.T) { 22 | rdy := config.NewReadinessProbe( 23 | &config.Config{ 24 | REST: config.REST{Enabled: true}, 25 | LeaderElection: config.LeaderElection{Enabled: false}, 26 | }, 27 | ) 28 | 29 | rdy.Wait() 30 | }) 31 | 32 | t.Run("wait for ready state", func(t *testing.T) { 33 | rdy := config.NewReadinessProbe( 34 | &config.Config{ 35 | REST: config.REST{Enabled: true}, 36 | LeaderElection: config.LeaderElection{Enabled: true}, 37 | }, 38 | ) 39 | 40 | if rdy.Running() { 41 | t.Error("should not be running until ready was called") 42 | } 43 | 44 | go func() { 45 | rdy.Wait() 46 | if !rdy.Running() { 47 | t.Error("should be running after ready was called") 48 | } 49 | }() 50 | 51 | rdy.Ready() 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/crd/api/policyreport/register.go: -------------------------------------------------------------------------------- 1 | package policyreport 2 | 3 | const ( 4 | GroupName = "wgpolicyk8s.io" 5 | ) 6 | -------------------------------------------------------------------------------- /pkg/crd/api/policyreport/v1alpha2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +groupName=policyreporter.kyverno.io 19 | 20 | package v1alpha2 21 | -------------------------------------------------------------------------------- /pkg/crd/api/policyreport/v1alpha2/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | 24 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport" 25 | ) 26 | 27 | // SchemeGroupVersion is group version used to register these objects 28 | var SchemeGroupVersion = schema.GroupVersion{Group: policyreport.GroupName, Version: "v1alpha2"} 29 | 30 | // Kind takes an unqualified kind and returns back a Group qualified GroupKind 31 | func Kind(kind string) schema.GroupKind { 32 | return SchemeGroupVersion.WithKind(kind).GroupKind() 33 | } 34 | 35 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 36 | func Resource(resource string) schema.GroupResource { 37 | return SchemeGroupVersion.WithResource(resource).GroupResource() 38 | } 39 | 40 | var ( 41 | // SchemeBuilder builds the scheme 42 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 43 | 44 | // AddToScheme adds all types of this clientset into the given scheme 45 | AddToScheme = SchemeBuilder.AddToScheme 46 | ) 47 | 48 | // Adds the list of known types to Scheme. 49 | func addKnownTypes(scheme *runtime.Scheme) error { 50 | scheme.AddKnownTypes(SchemeGroupVersion, 51 | &ClusterPolicyReport{}, 52 | &ClusterPolicyReportList{}, 53 | &PolicyReport{}, 54 | &PolicyReportList{}, 55 | ) 56 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/crd/api/targetconfig/register.go: -------------------------------------------------------------------------------- 1 | package targetconfig 2 | -------------------------------------------------------------------------------- /pkg/crd/api/targetconfig/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:openapi-gen=true 2 | // +k8s:deepcopy-gen=package 3 | // +groupName=policyreporter.kyverno.io 4 | 5 | package v1alpha1 6 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated clientset. 20 | package versioned 21 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated fake clientset. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | 28 | wgpolicyk8sv1alpha2 "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 29 | ) 30 | 31 | var scheme = runtime.NewScheme() 32 | var codecs = serializer.NewCodecFactory(scheme) 33 | 34 | var localSchemeBuilder = runtime.SchemeBuilder{ 35 | wgpolicyk8sv1alpha2.AddToScheme, 36 | } 37 | 38 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 39 | // of clientsets, like in: 40 | // 41 | // import ( 42 | // "k8s.io/client-go/kubernetes" 43 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 44 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 45 | // ) 46 | // 47 | // kclientset, _ := kubernetes.NewForConfig(c) 48 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 49 | // 50 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 51 | // correctly. 52 | var AddToScheme = localSchemeBuilder.AddToScheme 53 | 54 | func init() { 55 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 56 | utilruntime.Must(AddToScheme(scheme)) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package contains the scheme of the automatically generated clientset. 20 | package scheme 21 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package scheme 20 | 21 | import ( 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | 28 | wgpolicyk8sv1alpha2 "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 29 | ) 30 | 31 | var Scheme = runtime.NewScheme() 32 | var Codecs = serializer.NewCodecFactory(Scheme) 33 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 34 | var localSchemeBuilder = runtime.SchemeBuilder{ 35 | wgpolicyk8sv1alpha2.AddToScheme, 36 | } 37 | 38 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 39 | // of clientsets, like in: 40 | // 41 | // import ( 42 | // "k8s.io/client-go/kubernetes" 43 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 44 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 45 | // ) 46 | // 47 | // kclientset, _ := kubernetes.NewForConfig(c) 48 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 49 | // 50 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 51 | // correctly. 52 | var AddToScheme = localSchemeBuilder.AddToScheme 53 | 54 | func init() { 55 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 56 | utilruntime.Must(AddToScheme(Scheme)) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1alpha2 21 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2/fake/fake_policyreport_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | rest "k8s.io/client-go/rest" 23 | testing "k8s.io/client-go/testing" 24 | 25 | v1alpha2 "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2" 26 | ) 27 | 28 | type FakeWgpolicyk8sV1alpha2 struct { 29 | *testing.Fake 30 | } 31 | 32 | func (c *FakeWgpolicyk8sV1alpha2) ClusterPolicyReports() v1alpha2.ClusterPolicyReportInterface { 33 | return &FakeClusterPolicyReports{c} 34 | } 35 | 36 | func (c *FakeWgpolicyk8sV1alpha2) PolicyReports(namespace string) v1alpha2.PolicyReportInterface { 37 | return &FakePolicyReports{c, namespace} 38 | } 39 | 40 | // RESTClient returns a RESTClient that is used to communicate 41 | // with API server by this client implementation. 42 | func (c *FakeWgpolicyk8sV1alpha2) RESTClient() rest.Interface { 43 | var ret *rest.RESTClient 44 | return ret 45 | } 46 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1alpha2 20 | 21 | type ClusterPolicyReportExpansion interface{} 22 | 23 | type PolicyReportExpansion interface{} 24 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package internalinterfaces 20 | 21 | import ( 22 | time "time" 23 | 24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | cache "k8s.io/client-go/tools/cache" 27 | 28 | versioned "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned" 29 | ) 30 | 31 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 32 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 33 | 34 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 35 | type SharedInformerFactory interface { 36 | Start(stopCh <-chan struct{}) 37 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 38 | } 39 | 40 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 41 | type TweakListOptionsFunc func(*v1.ListOptions) 42 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/informers/externalversions/policyreport/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package policyreport 20 | 21 | import ( 22 | internalinterfaces "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/informers/externalversions/internalinterfaces" 23 | v1alpha2 "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/informers/externalversions/policyreport/v1alpha2" 24 | ) 25 | 26 | // Interface provides access to each of this group's versions. 27 | type Interface interface { 28 | // V1alpha2 provides access to shared informers for resources in V1alpha2. 29 | V1alpha2() v1alpha2.Interface 30 | } 31 | 32 | type group struct { 33 | factory internalinterfaces.SharedInformerFactory 34 | namespace string 35 | tweakListOptions internalinterfaces.TweakListOptionsFunc 36 | } 37 | 38 | // New returns a new Interface. 39 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 40 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 41 | } 42 | 43 | // V1alpha2 returns a new v1alpha2.Interface. 44 | func (g *group) V1alpha2() v1alpha2.Interface { 45 | return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/crd/client/policyreport/listers/policyreport/v1alpha2/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1alpha2 20 | 21 | // ClusterPolicyReportListerExpansion allows custom methods to be added to 22 | // ClusterPolicyReportLister. 23 | type ClusterPolicyReportListerExpansion interface{} 24 | 25 | // PolicyReportListerExpansion allows custom methods to be added to 26 | // PolicyReportLister. 27 | type PolicyReportListerExpansion interface{} 28 | 29 | // PolicyReportNamespaceListerExpansion allows custom methods to be added to 30 | // PolicyReportNamespaceLister. 31 | type PolicyReportNamespaceListerExpansion interface{} 32 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated fake clientset. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | 28 | policyreporterv1alpha1 "github.com/kyverno/policy-reporter/pkg/crd/api/targetconfig/v1alpha1" 29 | ) 30 | 31 | var scheme = runtime.NewScheme() 32 | var codecs = serializer.NewCodecFactory(scheme) 33 | 34 | var localSchemeBuilder = runtime.SchemeBuilder{ 35 | policyreporterv1alpha1.AddToScheme, 36 | } 37 | 38 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 39 | // of clientsets, like in: 40 | // 41 | // import ( 42 | // "k8s.io/client-go/kubernetes" 43 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 44 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 45 | // ) 46 | // 47 | // kclientset, _ := kubernetes.NewForConfig(c) 48 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 49 | // 50 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 51 | // correctly. 52 | var AddToScheme = localSchemeBuilder.AddToScheme 53 | 54 | func init() { 55 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 56 | utilruntime.Must(AddToScheme(scheme)) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package contains the scheme of the automatically generated clientset. 20 | package scheme 21 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/clientset/versioned/typed/targetconfig/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1alpha1 21 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/clientset/versioned/typed/targetconfig/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/clientset/versioned/typed/targetconfig/v1alpha1/fake/fake_targetconfig_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | rest "k8s.io/client-go/rest" 23 | testing "k8s.io/client-go/testing" 24 | 25 | v1alpha1 "github.com/kyverno/policy-reporter/pkg/crd/client/targetconfig/clientset/versioned/typed/targetconfig/v1alpha1" 26 | ) 27 | 28 | type FakePolicyreporterV1alpha1 struct { 29 | *testing.Fake 30 | } 31 | 32 | func (c *FakePolicyreporterV1alpha1) TargetConfigs(namespace string) v1alpha1.TargetConfigInterface { 33 | return &FakeTargetConfigs{c, namespace} 34 | } 35 | 36 | // RESTClient returns a RESTClient that is used to communicate 37 | // with API server by this client implementation. 38 | func (c *FakePolicyreporterV1alpha1) RESTClient() rest.Interface { 39 | var ret *rest.RESTClient 40 | return ret 41 | } 42 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/clientset/versioned/typed/targetconfig/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1alpha1 20 | 21 | type TargetConfigExpansion interface{} 22 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package internalinterfaces 20 | 21 | import ( 22 | time "time" 23 | 24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | cache "k8s.io/client-go/tools/cache" 27 | 28 | versioned "github.com/kyverno/policy-reporter/pkg/crd/client/targetconfig/clientset/versioned" 29 | ) 30 | 31 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 32 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 33 | 34 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 35 | type SharedInformerFactory interface { 36 | Start(stopCh <-chan struct{}) 37 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 38 | } 39 | 40 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 41 | type TweakListOptionsFunc func(*v1.ListOptions) 42 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/informers/externalversions/targetconfig/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package targetconfig 20 | 21 | import ( 22 | internalinterfaces "github.com/kyverno/policy-reporter/pkg/crd/client/targetconfig/informers/externalversions/internalinterfaces" 23 | v1alpha1 "github.com/kyverno/policy-reporter/pkg/crd/client/targetconfig/informers/externalversions/targetconfig/v1alpha1" 24 | ) 25 | 26 | // Interface provides access to each of this group's versions. 27 | type Interface interface { 28 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 29 | V1alpha1() v1alpha1.Interface 30 | } 31 | 32 | type group struct { 33 | factory internalinterfaces.SharedInformerFactory 34 | namespace string 35 | tweakListOptions internalinterfaces.TweakListOptionsFunc 36 | } 37 | 38 | // New returns a new Interface. 39 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 40 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 41 | } 42 | 43 | // V1alpha1 returns a new v1alpha1.Interface. 44 | func (g *group) V1alpha1() v1alpha1.Interface { 45 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/informers/externalversions/targetconfig/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1alpha1 20 | 21 | import ( 22 | internalinterfaces "github.com/kyverno/policy-reporter/pkg/crd/client/targetconfig/informers/externalversions/internalinterfaces" 23 | ) 24 | 25 | // Interface provides access to all the informers in this group version. 26 | type Interface interface { 27 | // TargetConfigs returns a TargetConfigInformer. 28 | TargetConfigs() TargetConfigInformer 29 | } 30 | 31 | type version struct { 32 | factory internalinterfaces.SharedInformerFactory 33 | namespace string 34 | tweakListOptions internalinterfaces.TweakListOptionsFunc 35 | } 36 | 37 | // New returns a new Interface. 38 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 39 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 40 | } 41 | 42 | // TargetConfigs returns a TargetConfigInformer. 43 | func (v *version) TargetConfigs() TargetConfigInformer { 44 | return &targetConfigInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 45 | } 46 | -------------------------------------------------------------------------------- /pkg/crd/client/targetconfig/listers/targetconfig/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1alpha1 20 | 21 | // TargetConfigListerExpansion allows custom methods to be added to 22 | // TargetConfigLister. 23 | type TargetConfigListerExpansion interface{} 24 | 25 | // TargetConfigNamespaceListerExpansion allows custom methods to be added to 26 | // TargetConfigNamespaceLister. 27 | type TargetConfigNamespaceListerExpansion interface{} 28 | -------------------------------------------------------------------------------- /pkg/database/views.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "github.com/uptrace/bun" 4 | 5 | type Category struct { 6 | bun.BaseModel `bun:"table:policy_report_filter,alias:f"` 7 | 8 | Source string 9 | Name string `bun:"category"` 10 | Result string 11 | Severity string 12 | Count int 13 | } 14 | 15 | type ResourceCategory struct { 16 | bun.BaseModel `bun:"table:policy_report_resource,alias:res"` 17 | 18 | Source string 19 | Name string `bun:"category"` 20 | Pass int 21 | Warn int 22 | Fail int 23 | Error int 24 | Skip int 25 | } 26 | 27 | type ResourceStatusCount struct { 28 | bun.BaseModel `bun:"table:policy_report_resource,alias:res"` 29 | Source string 30 | Pass int 31 | Warn int 32 | Fail int 33 | Error int 34 | Skip int 35 | } 36 | 37 | type ResourceSeverityCount struct { 38 | bun.BaseModel `bun:"table:policy_report_resource,alias:res"` 39 | Source string 40 | Info int 41 | Low int 42 | Medium int 43 | High int 44 | Critical int 45 | Unknown int 46 | } 47 | 48 | type StatusCount struct { 49 | bun.BaseModel `bun:"table:policy_report_filter,alias:f"` 50 | 51 | Source string 52 | Namespace string `bun:"resource_namespace"` 53 | Status string 54 | Count int 55 | } 56 | 57 | type SeverityCount struct { 58 | bun.BaseModel `bun:"table:policy_report_filter,alias:f"` 59 | 60 | Source string 61 | Namespace string `bun:"resource_namespace"` 62 | Severity string 63 | Count int 64 | } 65 | 66 | type ResultProperty struct { 67 | bun.BaseModel `bun:"table:policy_report_result,alias:pr"` 68 | 69 | Namespace string `bun:"resource_namespace"` 70 | Property string `bun:"property"` 71 | } 72 | -------------------------------------------------------------------------------- /pkg/email/client.go: -------------------------------------------------------------------------------- 1 | package email 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | mail "github.com/xhit/go-simple-mail/v2" 8 | ) 9 | 10 | func EncryptionFromString(enc string) mail.Encryption { 11 | switch strings.ToLower(enc) { 12 | case "ssl/tls": 13 | return mail.EncryptionSSLTLS 14 | case "starttls": 15 | return mail.EncryptionSTARTTLS 16 | default: 17 | return mail.EncryptionNone 18 | } 19 | } 20 | 21 | type Client struct { 22 | server *mail.SMTPServer 23 | from string 24 | } 25 | 26 | func (c *Client) Send(report Report, to []string) error { 27 | if len(to) > 1 { 28 | c.server.KeepAlive = true 29 | } 30 | 31 | client, err := c.server.Connect() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | msg := mail.NewMSG(). 37 | SetFrom(fmt.Sprintf("Policy Reporter <%s>", c.from)). 38 | AddTo(to...). 39 | SetSubject(report.Title) 40 | 41 | if strings.ToLower(report.Format) == "html" || report.Format == "" { 42 | msg.SetBody(mail.TextHTML, report.Message) 43 | } else { 44 | msg.SetBody(mail.TextPlain, report.Message) 45 | } 46 | 47 | if msg.Error != nil { 48 | return msg.Error 49 | } 50 | 51 | return msg.Send(client) 52 | } 53 | 54 | func NewClient(from string, server *mail.SMTPServer) *Client { 55 | return &Client{server: server, from: from} 56 | } 57 | -------------------------------------------------------------------------------- /pkg/email/client_test.go: -------------------------------------------------------------------------------- 1 | package email_test 2 | 3 | import ( 4 | "testing" 5 | 6 | mail "github.com/xhit/go-simple-mail/v2" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/email" 9 | ) 10 | 11 | func Test_EncryptionFromString(t *testing.T) { 12 | t.Run("EncryptionFromString.SSLTLS", func(t *testing.T) { 13 | encryption := email.EncryptionFromString("ssl/tls") 14 | if encryption != mail.EncryptionSSLTLS { 15 | t.Errorf("Unexpected encryption mapping: %d", encryption) 16 | } 17 | }) 18 | t.Run("EncryptionFromString.STARTTLS", func(t *testing.T) { 19 | encryption := email.EncryptionFromString("starttls") 20 | if encryption != mail.EncryptionSTARTTLS { 21 | t.Errorf("Unexpected encryption mapping: %d", encryption) 22 | } 23 | }) 24 | t.Run("EncryptionFromString.Default", func(t *testing.T) { 25 | encryption := email.EncryptionFromString("") 26 | if encryption != mail.EncryptionNone { 27 | t.Errorf("Unexpected encryption mapping: %d", encryption) 28 | } 29 | }) 30 | } 31 | 32 | func Test_NewClient(t *testing.T) { 33 | client := email.NewClient("policy-reporter@kyverno.io", nil) 34 | if client == nil { 35 | t.Errorf("Unexpected client result") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/email/filter.go: -------------------------------------------------------------------------------- 1 | package email 2 | 3 | import ( 4 | "context" 5 | 6 | "go.uber.org/zap" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/kubernetes/namespaces" 9 | "github.com/kyverno/policy-reporter/pkg/validate" 10 | ) 11 | 12 | type Filter struct { 13 | client namespaces.Client 14 | namespace validate.RuleSets 15 | sources validate.RuleSets 16 | } 17 | 18 | func (f Filter) ValidateSource(source string) bool { 19 | return validate.MatchRuleSet(source, f.sources) 20 | } 21 | 22 | func (f Filter) ValidateNamespace(namespace string) bool { 23 | ruleset := f.namespace 24 | 25 | if len(f.namespace.Selector) > 0 { 26 | list, err := f.client.List(context.Background(), f.namespace.Selector) 27 | if err != nil { 28 | zap.L().Error("failed to resolve namespace selector", zap.Error(err)) 29 | } 30 | 31 | ruleset = validate.RuleSets{ 32 | Include: list, 33 | } 34 | } 35 | 36 | return validate.Namespace(namespace, ruleset) 37 | } 38 | 39 | func NewFilter(client namespaces.Client, namespaces, sources validate.RuleSets) Filter { 40 | return Filter{client, namespaces, sources} 41 | } 42 | -------------------------------------------------------------------------------- /pkg/email/filter_test.go: -------------------------------------------------------------------------------- 1 | package email_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/email" 7 | "github.com/kyverno/policy-reporter/pkg/validate" 8 | ) 9 | 10 | func Test_Filters(t *testing.T) { 11 | t.Run("Validate Default", func(t *testing.T) { 12 | filter := email.NewFilter(nil, validate.RuleSets{}, validate.RuleSets{}) 13 | 14 | if !filter.ValidateNamespace("test") { 15 | t.Errorf("Unexpected Validation Result without configured rules") 16 | } 17 | if !filter.ValidateSource("Kyverno") { 18 | t.Errorf("Unexpected Validation Result without configured rules") 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/email/functions.go: -------------------------------------------------------------------------------- 1 | package email 2 | 3 | import "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 4 | 5 | const ( 6 | PassColor = "#198754" 7 | WarnColor = "#fd7e14" 8 | FailColor = "#dc3545" 9 | ErrorColor = "#b02a37" 10 | DefaultColor = "#cccccc" 11 | ) 12 | 13 | func ColorFromStatus(status string) string { 14 | switch status { 15 | case v1alpha2.StatusPass: 16 | return PassColor 17 | case v1alpha2.StatusWarn: 18 | return WarnColor 19 | case v1alpha2.StatusFail: 20 | return FailColor 21 | case v1alpha2.StatusError: 22 | return ErrorColor 23 | default: 24 | return DefaultColor 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/email/functions_test.go: -------------------------------------------------------------------------------- 1 | package email_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 7 | "github.com/kyverno/policy-reporter/pkg/email" 8 | ) 9 | 10 | func Test_ColorFromStatus(t *testing.T) { 11 | t.Run("ColorFromStatus.Pass", func(t *testing.T) { 12 | color := email.ColorFromStatus(v1alpha2.StatusPass) 13 | if color != email.PassColor { 14 | t.Errorf("Unexpected pass color: %s", color) 15 | } 16 | }) 17 | t.Run("ColorFromStatus.Warn", func(t *testing.T) { 18 | color := email.ColorFromStatus(v1alpha2.StatusWarn) 19 | if color != email.WarnColor { 20 | t.Errorf("Unexpected warn color: %s", color) 21 | } 22 | }) 23 | t.Run("ColorFromStatus.Fail", func(t *testing.T) { 24 | color := email.ColorFromStatus(v1alpha2.StatusFail) 25 | if color != email.FailColor { 26 | t.Errorf("Unexpected fail color: %s", color) 27 | } 28 | }) 29 | t.Run("ColorFromStatus.Error", func(t *testing.T) { 30 | color := email.ColorFromStatus(v1alpha2.StatusError) 31 | if color != email.ErrorColor { 32 | t.Errorf("Unexpected error color: %s", color) 33 | } 34 | }) 35 | t.Run("ColorFromStatus.Default", func(t *testing.T) { 36 | color := email.ColorFromStatus("") 37 | if color != email.DefaultColor { 38 | t.Errorf("Unexpected error color: %s", color) 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/email/model.go: -------------------------------------------------------------------------------- 1 | package email 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Report struct { 8 | Title string 9 | Message string 10 | Format string 11 | ClusterName string 12 | } 13 | 14 | type Reporter interface { 15 | Report(ctx context.Context) (Report, error) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/email/summary/fixtures_test.go: -------------------------------------------------------------------------------- 1 | package summary_test 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned/fake" 7 | v1alpha2client "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2" 8 | "github.com/kyverno/policy-reporter/pkg/email" 9 | "github.com/kyverno/policy-reporter/pkg/validate" 10 | ) 11 | 12 | var ( 13 | filter = email.NewFilter(nil, validate.RuleSets{}, validate.RuleSets{}) 14 | logger = zap.NewNop() 15 | ) 16 | 17 | func NewFakeClient() (v1alpha2client.Wgpolicyk8sV1alpha2Interface, v1alpha2client.PolicyReportInterface, v1alpha2client.ClusterPolicyReportInterface) { 18 | client := fake.NewSimpleClientset().Wgpolicyk8sV1alpha2() 19 | 20 | return client, client.PolicyReports("test"), client.ClusterPolicyReports() 21 | } 22 | -------------------------------------------------------------------------------- /pkg/email/summary/model.go: -------------------------------------------------------------------------------- 1 | package summary 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 7 | ) 8 | 9 | type Summary struct { 10 | Skip int 11 | Pass int 12 | Warn int 13 | Fail int 14 | Error int 15 | } 16 | 17 | type Source struct { 18 | Name string 19 | ClusterScopeSummary *Summary 20 | NamespaceScopeSummary map[string]*Summary 21 | ClusterReports bool 22 | 23 | mx *sync.Mutex 24 | } 25 | 26 | func (s *Source) AddClusterSummary(sum v1alpha2.PolicyReportSummary) { 27 | s.ClusterScopeSummary.Skip += sum.Skip 28 | s.ClusterScopeSummary.Pass += sum.Pass 29 | s.ClusterScopeSummary.Warn += sum.Warn 30 | s.ClusterScopeSummary.Fail += sum.Fail 31 | s.ClusterScopeSummary.Error += sum.Error 32 | } 33 | 34 | func (s *Source) AddNamespacedSummary(ns string, sum v1alpha2.PolicyReportSummary) { 35 | s.mx.Lock() 36 | defer s.mx.Unlock() 37 | if d, ok := s.NamespaceScopeSummary[ns]; ok { 38 | d.Skip += sum.Skip 39 | d.Pass += sum.Pass 40 | d.Warn += sum.Warn 41 | d.Fail += sum.Fail 42 | d.Error += sum.Error 43 | } else { 44 | s.NamespaceScopeSummary[ns] = &Summary{ 45 | Skip: sum.Skip, 46 | Pass: sum.Pass, 47 | Fail: sum.Fail, 48 | Warn: sum.Warn, 49 | Error: sum.Error, 50 | } 51 | } 52 | } 53 | 54 | func NewSource(name string, clusterReports bool) *Source { 55 | return &Source{ 56 | Name: name, 57 | ClusterScopeSummary: &Summary{}, 58 | NamespaceScopeSummary: map[string]*Summary{}, 59 | ClusterReports: clusterReports, 60 | mx: new(sync.Mutex), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/email/summary/reporter.go: -------------------------------------------------------------------------------- 1 | package summary 2 | 3 | import ( 4 | "html/template" 5 | "strings" 6 | "time" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/email" 9 | ) 10 | 11 | type Reporter struct { 12 | templateDir string 13 | clusterName string 14 | titlePrefix string 15 | } 16 | 17 | func (o *Reporter) Report(sources []Source, format string) (email.Report, error) { 18 | b := new(strings.Builder) 19 | 20 | templ, err := template.ParseFiles(o.templateDir + "/summary.html") 21 | if err != nil { 22 | return email.Report{}, err 23 | } 24 | 25 | err = templ.Execute(b, struct { 26 | Sources []Source 27 | ClusterName string 28 | TitlePrefix string 29 | }{ 30 | Sources: sources, 31 | ClusterName: o.clusterName, 32 | TitlePrefix: o.titlePrefix, 33 | }) 34 | if err != nil { 35 | return email.Report{}, err 36 | } 37 | 38 | titleCluster := " " 39 | if o.clusterName != "" { 40 | titleCluster = " on " + o.clusterName + " " 41 | } 42 | 43 | return email.Report{ 44 | ClusterName: o.clusterName, 45 | Title: o.titlePrefix + " (summary)" + titleCluster + "from " + time.Now().Format("2006-01-02"), 46 | Message: b.String(), 47 | Format: format, 48 | }, nil 49 | } 50 | 51 | func NewReporter(templateDir, clusterName string, titlePrefix string) *Reporter { 52 | return &Reporter{templateDir, clusterName, titlePrefix} 53 | } 54 | -------------------------------------------------------------------------------- /pkg/email/summary/reporter_test.go: -------------------------------------------------------------------------------- 1 | package summary_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | 12 | "github.com/kyverno/policy-reporter/pkg/email/summary" 13 | "github.com/kyverno/policy-reporter/pkg/fixtures" 14 | ) 15 | 16 | func Test_CreateReport(t *testing.T) { 17 | ctx := context.Background() 18 | 19 | client, pClient, cClient := NewFakeClient() 20 | 21 | _, _ = pClient.Create(ctx, fixtures.DefaultPolicyReport, v1.CreateOptions{}) 22 | _, _ = pClient.Create(ctx, fixtures.EmptyPolicyReport, v1.CreateOptions{}) 23 | _, _ = client.PolicyReports("kyverno").Create(ctx, fixtures.KyvernoPolicyReport, v1.CreateOptions{}) 24 | 25 | _, _ = cClient.Create(ctx, fixtures.ClusterPolicyReport, v1.CreateOptions{}) 26 | _, _ = cClient.Create(ctx, fixtures.EmptyClusterPolicyReport, v1.CreateOptions{}) 27 | _, _ = cClient.Create(ctx, fixtures.KyvernoClusterPolicyReport, v1.CreateOptions{}) 28 | 29 | generator := summary.NewGenerator(client, filter, true) 30 | data, err := generator.GenerateData(ctx) 31 | if err != nil { 32 | t.Fatalf("unexpected error: %s", err) 33 | } 34 | 35 | path, err := os.Getwd() 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | 40 | fmt.Println(path) 41 | 42 | reporter := summary.NewReporter("../../../templates", "Cluster", "Report") 43 | report, err := reporter.Report(data, "html") 44 | if err != nil { 45 | t.Fatalf("unexpected error: %s", err) 46 | } 47 | 48 | if report.Message == "" { 49 | t.Fatal("expected validate report message") 50 | } 51 | if report.ClusterName != "Cluster" { 52 | t.Fatal("expected clustername to be set") 53 | } 54 | expected := "Report (summary) on Cluster from " + time.Now().Format("2006-01-02") 55 | if report.Title != expected { 56 | t.Fatalf("expected title to be '%s', got %s", expected, report.Title) 57 | } 58 | if report.Format != "html" { 59 | t.Fatal("expected format to be set") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/email/violations/fixtures_test.go: -------------------------------------------------------------------------------- 1 | package violations_test 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned/fake" 7 | v1alpha2client "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2" 8 | "github.com/kyverno/policy-reporter/pkg/email" 9 | "github.com/kyverno/policy-reporter/pkg/validate" 10 | ) 11 | 12 | var ( 13 | filter = email.NewFilter(nil, validate.RuleSets{}, validate.RuleSets{}) 14 | logger = zap.NewNop() 15 | ) 16 | 17 | func NewFakeClient() (v1alpha2client.Wgpolicyk8sV1alpha2Interface, v1alpha2client.PolicyReportInterface, v1alpha2client.ClusterPolicyReportInterface) { 18 | client := fake.NewSimpleClientset().Wgpolicyk8sV1alpha2() 19 | 20 | return client, client.PolicyReports("test"), client.ClusterPolicyReports() 21 | } 22 | -------------------------------------------------------------------------------- /pkg/email/violations/reporter.go: -------------------------------------------------------------------------------- 1 | package violations 2 | 3 | import ( 4 | "html/template" 5 | "strings" 6 | "time" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/email" 9 | "github.com/kyverno/policy-reporter/pkg/helper" 10 | ) 11 | 12 | type Reporter struct { 13 | templateDir string 14 | clusterName string 15 | titlePrefix string 16 | } 17 | 18 | func (o *Reporter) Report(sources []Source, format string) (email.Report, error) { 19 | b := new(strings.Builder) 20 | 21 | vioTempl := template.New("violations.html").Funcs(template.FuncMap{ 22 | "color": email.ColorFromStatus, 23 | "title": helper.Title, 24 | "hasViolations": func(results map[string][]Result) bool { 25 | return (len(results["warn"]) + len(results["fail"]) + len(results["error"])) > 0 26 | }, 27 | "lenNamespaceResults": func(source Source, ns, status string) int { 28 | return len(source.NamespaceResults[ns][status]) 29 | }, 30 | }) 31 | 32 | templ, err := vioTempl.ParseFiles(o.templateDir + "/violations.html") 33 | if err != nil { 34 | return email.Report{}, err 35 | } 36 | 37 | err = templ.Execute(b, struct { 38 | Sources []Source 39 | Status []string 40 | ClusterName string 41 | TitlePrefix string 42 | }{ 43 | Sources: sources, 44 | Status: []string{"warn", "fail", "error"}, 45 | ClusterName: o.clusterName, 46 | TitlePrefix: o.titlePrefix, 47 | }) 48 | if err != nil { 49 | return email.Report{}, err 50 | } 51 | 52 | titleCluster := " " 53 | if o.clusterName != "" { 54 | titleCluster = " on " + o.clusterName + " " 55 | } 56 | 57 | return email.Report{ 58 | ClusterName: o.clusterName, 59 | Title: o.titlePrefix + " (violations)" + titleCluster + "from " + time.Now().Format("2006-01-02"), 60 | Message: b.String(), 61 | Format: format, 62 | }, nil 63 | } 64 | 65 | func NewReporter(templateDir string, clusterName string, titlePrefix string) *Reporter { 66 | return &Reporter{templateDir, clusterName, titlePrefix} 67 | } 68 | -------------------------------------------------------------------------------- /pkg/email/violations/reporter_test.go: -------------------------------------------------------------------------------- 1 | package violations_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | 12 | "github.com/kyverno/policy-reporter/pkg/email/violations" 13 | "github.com/kyverno/policy-reporter/pkg/fixtures" 14 | ) 15 | 16 | func Test_CreateReport(t *testing.T) { 17 | ctx := context.Background() 18 | 19 | client, pClient, cClient := NewFakeClient() 20 | 21 | _, _ = pClient.Create(ctx, fixtures.DefaultPolicyReport, v1.CreateOptions{}) 22 | _, _ = pClient.Create(ctx, fixtures.EmptyPolicyReport, v1.CreateOptions{}) 23 | _, _ = client.PolicyReports("kyverno").Create(ctx, fixtures.KyvernoPolicyReport, v1.CreateOptions{}) 24 | 25 | _, _ = cClient.Create(ctx, fixtures.ClusterPolicyReport, v1.CreateOptions{}) 26 | _, _ = cClient.Create(ctx, fixtures.EmptyClusterPolicyReport, v1.CreateOptions{}) 27 | _, _ = cClient.Create(ctx, fixtures.KyvernoClusterPolicyReport, v1.CreateOptions{}) 28 | 29 | generator := violations.NewGenerator(client, filter, true) 30 | data, err := generator.GenerateData(ctx) 31 | if err != nil { 32 | t.Fatalf("unexpected error: %s", err) 33 | } 34 | 35 | path, err := os.Getwd() 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | 40 | fmt.Println(path) 41 | 42 | reporter := violations.NewReporter("../../../templates", "Cluster", "Report") 43 | report, err := reporter.Report(data, "html") 44 | if err != nil { 45 | t.Fatalf("unexpected error: %s", err) 46 | } 47 | 48 | if report.Message == "" { 49 | t.Fatal("expected validate report message") 50 | } 51 | if report.ClusterName != "Cluster" { 52 | t.Fatal("expected clustername to be set") 53 | } 54 | expected := "Report (violations) on Cluster from " + time.Now().Format("2006-01-02") 55 | if report.Title != expected { 56 | t.Fatalf("expected titleprefix to be '%s', got '%s'", expected, report.Title) 57 | } 58 | if report.Format != "html" { 59 | t.Fatal("expected format to be set") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/filters/filter.go: -------------------------------------------------------------------------------- 1 | package filters 2 | 3 | type ValueFilter struct { 4 | // +optional 5 | Include []string `mapstructure:"include" json:"include"` 6 | // +optional 7 | Exclude []string `mapstructure:"exclude" json:"exclude"` 8 | // +optional 9 | Selector map[string]string `mapstructure:"selector" json:"selector"` 10 | } 11 | 12 | type Filter struct { 13 | // +optional 14 | Namespaces ValueFilter `mapstructure:"namespaces" json:"namespaces"` 15 | // +optional 16 | Status ValueFilter `mapstructure:"status" json:"status"` 17 | // +optional 18 | Severities ValueFilter `mapstructure:"severities" json:"severities"` 19 | // +optional 20 | Policies ValueFilter `mapstructure:"policies" json:"policies"` 21 | // +optional 22 | Sources ValueFilter `mapstructure:"sources" json:"sources"` 23 | // +optional 24 | ReportLabels ValueFilter `mapstructure:"reportLabels" json:"reportLabels"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/fixtures/logger.go: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | import "go.uber.org/zap" 4 | 5 | var Logger = zap.NewNop() 6 | -------------------------------------------------------------------------------- /pkg/helper/chunk_slice.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | func ChunkSlice[K interface{}](slice []K, chunkSize int) [][]K { 4 | var chunks [][]K 5 | for i := 0; i < len(slice); i += chunkSize { 6 | end := i + chunkSize 7 | 8 | if end > len(slice) { 9 | end = len(slice) 10 | } 11 | 12 | chunks = append(chunks, slice[i:end]) 13 | } 14 | 15 | return chunks 16 | } 17 | -------------------------------------------------------------------------------- /pkg/helper/chunk_slice_test.go: -------------------------------------------------------------------------------- 1 | package helper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/helper" 9 | ) 10 | 11 | func TestChunkSize(t *testing.T) { 12 | chunks := helper.ChunkSlice([]int{1, 2, 3, 4, 5, 6, 7}, 3) 13 | 14 | assert.Len(t, chunks, 3) 15 | assert.Equal(t, []int{1, 2, 3}, chunks[0]) 16 | assert.Equal(t, []int{4, 5, 6}, chunks[1]) 17 | assert.Equal(t, []int{7}, chunks[2]) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/helper/first.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | func First[T any](list []*T) *T { 4 | if len(list) == 0 { 5 | return nil 6 | } 7 | 8 | return list[0] 9 | } 10 | -------------------------------------------------------------------------------- /pkg/helper/first_test.go: -------------------------------------------------------------------------------- 1 | package helper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/helper" 9 | ) 10 | 11 | type item struct { 12 | val int 13 | } 14 | 15 | func TestFirst(t *testing.T) { 16 | t.Run("return nil for empty list", func(t *testing.T) { 17 | assert.Nil(t, helper.First([]*item{})) 18 | }) 19 | 20 | t.Run("return first item", func(t *testing.T) { 21 | assert.Equal(t, 0, helper.First([]*item{{val: 0}, {val: 1}}).val) 22 | assert.Equal(t, 3, helper.First([]*item{{val: 3}, {val: 1}, {val: 2}}).val) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/helper/http.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "html" 7 | "net/http" 8 | ) 9 | 10 | func SendJSONResponse(w http.ResponseWriter, list interface{}, err error) { 11 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 12 | if err != nil { 13 | w.WriteHeader(http.StatusInternalServerError) 14 | fmt.Fprintf(w, `{ "message": "%s" }`, html.EscapeString(err.Error())) 15 | 16 | return 17 | } 18 | 19 | if err := json.NewEncoder(w).Encode(list); err != nil { 20 | w.WriteHeader(http.StatusInternalServerError) 21 | fmt.Fprintf(w, `{ "message": "%s" }`, html.EscapeString(err.Error())) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/helper/http_test.go: -------------------------------------------------------------------------------- 1 | package helper_test 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | "github.com/kyverno/policy-reporter/pkg/helper" 13 | ) 14 | 15 | func TestSendJSONResponse(t *testing.T) { 16 | t.Run("success response", func(t *testing.T) { 17 | w := httptest.NewRecorder() 18 | 19 | helper.SendJSONResponse(w, []string{"default", "user"}, nil) 20 | 21 | assert.Equal(t, http.StatusOK, w.Code) 22 | 23 | resp := make([]string, 0, 2) 24 | 25 | json.NewDecoder(w.Body).Decode(&resp) 26 | 27 | assert.Equal(t, []string{"default", "user"}, resp) 28 | }) 29 | 30 | t.Run("error response", func(t *testing.T) { 31 | w := httptest.NewRecorder() 32 | 33 | helper.SendJSONResponse(w, nil, errors.New("error")) 34 | 35 | assert.Equal(t, http.StatusInternalServerError, w.Code) 36 | 37 | resp := make(map[string]string, 0) 38 | 39 | json.NewDecoder(w.Body).Decode(&resp) 40 | 41 | assert.Equal(t, map[string]string{"message": "error"}, resp) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/helper/title.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "golang.org/x/text/cases" 5 | "golang.org/x/text/language" 6 | ) 7 | 8 | var caser = cases.Title(language.English, cases.NoLower) 9 | 10 | func Title(s string) string { 11 | return caser.String(s) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/helper/title_test.go: -------------------------------------------------------------------------------- 1 | package helper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/helper" 9 | ) 10 | 11 | func TestTitle(t *testing.T) { 12 | assert.Equal(t, "Kyverno", helper.Title("kyverno")) 13 | assert.Equal(t, "Trivy Vulnerability", helper.Title("trivy vulnerability")) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/helper/utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func Contains(source string, sources []string) bool { 8 | for _, s := range sources { 9 | if strings.EqualFold(s, source) { 10 | return true 11 | } 12 | } 13 | 14 | return false 15 | } 16 | 17 | func ToList[T any](mapping map[string]T) []T { 18 | list := make([]T, 0, len(mapping)) 19 | for _, i := range mapping { 20 | list = append(list, i) 21 | } 22 | 23 | return list 24 | } 25 | 26 | func Map[T any, R any](source []T, cb func(T) R) []R { 27 | list := make([]R, 0, len(source)) 28 | for _, i := range source { 29 | list = append(list, cb(i)) 30 | } 31 | 32 | return list 33 | } 34 | 35 | func MapSlice[T any, R any, Z comparable](source map[Z]T, cb func(T) R) []R { 36 | list := make([]R, 0, len(source)) 37 | for _, i := range source { 38 | list = append(list, cb(i)) 39 | } 40 | 41 | return list 42 | } 43 | 44 | func ConvertMap(m map[string]any) map[string]string { 45 | n := make(map[string]string, len(m)) 46 | for k, v := range m { 47 | if l, ok := v.(string); ok { 48 | n[k] = l 49 | } 50 | } 51 | 52 | return n 53 | } 54 | 55 | func Defaults(s, f string) string { 56 | if s != "" { 57 | return s 58 | } 59 | 60 | return f 61 | } 62 | 63 | func ToPointer[T any](s T) *T { 64 | return &s 65 | } 66 | 67 | func Filter[T any](s []T, keep func(T) bool) []T { 68 | d := make([]T, 0, len(s)) 69 | for _, n := range s { 70 | if keep(n) { 71 | d = append(d, n) 72 | } 73 | } 74 | return d 75 | } 76 | 77 | func Find[T any](s []T, keep func(T) bool, fallback T) T { 78 | for _, n := range s { 79 | if keep(n) { 80 | return n 81 | } 82 | } 83 | return fallback 84 | } 85 | -------------------------------------------------------------------------------- /pkg/kubernetes/cache.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | type Cache interface { 4 | AddItem(string, interface{}) 5 | GetItem(string) (interface{}, bool) 6 | RemoveItem(string) 7 | } 8 | -------------------------------------------------------------------------------- /pkg/kubernetes/debouncer.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/kyverno/policy-reporter/pkg/report" 8 | ) 9 | 10 | type Debouncer interface { 11 | Add(e report.LifecycleEvent) 12 | } 13 | 14 | type debouncer struct { 15 | waitDuration time.Duration 16 | events map[string]report.LifecycleEvent 17 | publisher report.EventPublisher 18 | mutx *sync.Mutex 19 | } 20 | 21 | func (d *debouncer) Add(event report.LifecycleEvent) { 22 | _, ok := d.events[event.PolicyReport.GetID()] 23 | if event.Type != report.Updated && ok { 24 | d.mutx.Lock() 25 | delete(d.events, event.PolicyReport.GetID()) 26 | d.mutx.Unlock() 27 | } 28 | 29 | if event.Type != report.Updated { 30 | d.publisher.Publish(event) 31 | return 32 | } 33 | 34 | if len(event.PolicyReport.GetResults()) == 0 && !ok { 35 | d.mutx.Lock() 36 | d.events[event.PolicyReport.GetID()] = event 37 | d.mutx.Unlock() 38 | 39 | go func() { 40 | time.Sleep(d.waitDuration) 41 | 42 | d.mutx.Lock() 43 | if event, ok := d.events[event.PolicyReport.GetID()]; ok { 44 | d.publisher.Publish(event) 45 | delete(d.events, event.PolicyReport.GetID()) 46 | } 47 | d.mutx.Unlock() 48 | }() 49 | 50 | return 51 | } 52 | 53 | if len(event.PolicyReport.GetResults()) > 0 && ok { 54 | d.mutx.Lock() 55 | delete(d.events, event.PolicyReport.GetID()) 56 | d.mutx.Unlock() 57 | } 58 | 59 | d.publisher.Publish(event) 60 | } 61 | 62 | func NewDebouncer(waitDuration time.Duration, publisher report.EventPublisher) Debouncer { 63 | return &debouncer{ 64 | waitDuration: waitDuration, 65 | events: make(map[string]report.LifecycleEvent), 66 | mutx: new(sync.Mutex), 67 | publisher: publisher, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pkg/kubernetes/fixtures_test.go: -------------------------------------------------------------------------------- 1 | package kubernetes_test 2 | 3 | import ( 4 | "sync" 5 | 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | metafake "k8s.io/client-go/metadata/fake" 8 | 9 | pr "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 10 | "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned/fake" 11 | v1alpha2client "github.com/kyverno/policy-reporter/pkg/crd/client/policyreport/clientset/versioned/typed/policyreport/v1alpha2" 12 | "github.com/kyverno/policy-reporter/pkg/report" 13 | ) 14 | 15 | func NewFakeMetaClient() (*metafake.FakeMetadataClient, metafake.MetadataClient, metafake.MetadataClient) { 16 | schema := metafake.NewTestScheme() 17 | metav1.AddMetaToScheme(schema) 18 | 19 | client := metafake.NewSimpleMetadataClient(schema) 20 | return client, client.Resource(pr.SchemeGroupVersion.WithResource("policyreports")).Namespace("test").(metafake.MetadataClient), client.Resource(pr.SchemeGroupVersion.WithResource("clusterpolicyreports")).(metafake.MetadataClient) 21 | } 22 | 23 | func NewFakeClient() (*fake.Clientset, v1alpha2client.PolicyReportInterface, v1alpha2client.ClusterPolicyReportInterface) { 24 | client := fake.NewSimpleClientset() 25 | 26 | return client, client.Wgpolicyk8sV1alpha2().PolicyReports("test"), client.Wgpolicyk8sV1alpha2().ClusterPolicyReports() 27 | } 28 | 29 | type store struct { 30 | store []report.LifecycleEvent 31 | rwm *sync.RWMutex 32 | } 33 | 34 | func (s *store) Add(r report.LifecycleEvent) { 35 | s.rwm.Lock() 36 | defer s.rwm.Unlock() 37 | s.store = append(s.store, r) 38 | } 39 | 40 | func (s *store) Get(index int) report.LifecycleEvent { 41 | return s.store[index] 42 | } 43 | 44 | func (s *store) List() []report.LifecycleEvent { 45 | return s.store 46 | } 47 | 48 | func newStore(size int) *store { 49 | return &store{ 50 | store: make([]report.LifecycleEvent, 0, size), 51 | rwm: &sync.RWMutex{}, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/kubernetes/jobs/client.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "context" 5 | 6 | batchv1 "k8s.io/api/batch/v1" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | v1 "k8s.io/client-go/kubernetes/typed/batch/v1" 10 | 11 | "github.com/kyverno/policy-reporter/pkg/kubernetes" 12 | ) 13 | 14 | type Client interface { 15 | Get(scope *corev1.ObjectReference) (*batchv1.Job, error) 16 | } 17 | 18 | type k8sClient struct { 19 | client v1.BatchV1Interface 20 | } 21 | 22 | func (c *k8sClient) Get(scope *corev1.ObjectReference) (*batchv1.Job, error) { 23 | return kubernetes.Retry(func() (*batchv1.Job, error) { 24 | return c.client.Jobs(scope.Namespace).Get(context.Background(), scope.Name, metav1.GetOptions{}) 25 | }) 26 | } 27 | 28 | func NewClient(client v1.BatchV1Interface) Client { 29 | return &k8sClient{ 30 | client: client, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/kubernetes/pods/client.go: -------------------------------------------------------------------------------- 1 | package pods 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | v1 "k8s.io/client-go/kubernetes/typed/core/v1" 9 | 10 | "github.com/kyverno/policy-reporter/pkg/kubernetes" 11 | ) 12 | 13 | type Client interface { 14 | Get(scope *corev1.ObjectReference) (*corev1.Pod, error) 15 | } 16 | 17 | type k8sClient struct { 18 | client v1.CoreV1Interface 19 | } 20 | 21 | func (c *k8sClient) Get(scope *corev1.ObjectReference) (*corev1.Pod, error) { 22 | return kubernetes.Retry(func() (*corev1.Pod, error) { 23 | return c.client.Pods(scope.Namespace).Get(context.Background(), scope.Name, metav1.GetOptions{}) 24 | }) 25 | } 26 | 27 | func NewClient(client v1.CoreV1Interface) Client { 28 | return &k8sClient{ 29 | client: client, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg/kubernetes/retry.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/api/errors" 5 | "k8s.io/client-go/util/retry" 6 | ) 7 | 8 | func Retry[T any](cb func() (T, error)) (T, error) { 9 | var value T 10 | 11 | err := retry.OnError(retry.DefaultRetry, func(err error) bool { 12 | if _, ok := err.(errors.APIStatus); !ok { 13 | return true 14 | } 15 | 16 | if ok := errors.IsTimeout(err); ok { 17 | return true 18 | } 19 | 20 | if ok := errors.IsServerTimeout(err); ok { 21 | return true 22 | } 23 | 24 | if ok := errors.IsServiceUnavailable(err); ok { 25 | return true 26 | } 27 | 28 | return false 29 | }, func() error { 30 | v, err := cb() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | value = v 36 | 37 | return nil 38 | }) 39 | 40 | return value, err 41 | } 42 | -------------------------------------------------------------------------------- /pkg/leaderelection/client_test.go: -------------------------------------------------------------------------------- 1 | package leaderelection_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "k8s.io/client-go/kubernetes/typed/coordination/v1/fake" 10 | 11 | "github.com/kyverno/policy-reporter/pkg/leaderelection" 12 | ) 13 | 14 | func TestClient(t *testing.T) { 15 | client := leaderelection.New(&fake.FakeCoordinationV1{}, "policy-reporter", "namespace", "pod-123", time.Second, time.Second, time.Second, true) 16 | 17 | if client == nil { 18 | t.Fatal("failed to create leaderelection client") 19 | } 20 | 21 | var isLeader bool 22 | client.RegisterOnNew(func(currentID, lockID string) { 23 | isLeader = currentID == lockID 24 | }) 25 | 26 | client.RegisterOnStart(func(c context.Context) {}) 27 | client.RegisterOnStop(func() {}) 28 | 29 | lock := client.CreateLock() 30 | 31 | assert.Equal(t, "policy-reporter", lock.LeaseMeta.Name) 32 | assert.Equal(t, "namespace", lock.LeaseMeta.Namespace) 33 | assert.Equal(t, "pod-123", lock.LockConfig.Identity) 34 | 35 | assert.False(t, isLeader) 36 | 37 | config := client.CreateConfig() 38 | 39 | config.Callbacks.OnNewLeader("pod-123") 40 | 41 | assert.True(t, isLeader) 42 | assert.Equal(t, time.Second, config.LeaseDuration) 43 | assert.Equal(t, time.Second, config.RenewDeadline) 44 | assert.Equal(t, time.Second, config.RetryPeriod) 45 | assert.True(t, config.ReleaseOnCancel) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/listener/cleanup.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/report" 7 | "github.com/kyverno/policy-reporter/pkg/target" 8 | ) 9 | 10 | const CleanUpListener = "cleanup_listener" 11 | 12 | func NewCleanupListener(ctx context.Context, targets *target.Collection) report.PolicyReportListener { 13 | return func(event report.LifecycleEvent) { 14 | if event.Type == report.Added { 15 | return 16 | } 17 | 18 | for _, handler := range targets.SyncClients() { 19 | handler.CleanUp(ctx, event.PolicyReport) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/listener/cleanup_test.go: -------------------------------------------------------------------------------- 1 | package listener_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/listener" 9 | "github.com/kyverno/policy-reporter/pkg/report" 10 | "github.com/kyverno/policy-reporter/pkg/target" 11 | ) 12 | 13 | func Test_CleanupListener(t *testing.T) { 14 | t.Run("Execute Cleanup Handler", func(t *testing.T) { 15 | c := &client{cleanup: true} 16 | 17 | slistener := listener.NewCleanupListener(ctx, target.NewCollection(&target.Target{Client: c})) 18 | slistener(report.LifecycleEvent{Type: report.Deleted, PolicyReport: preport1}) 19 | 20 | assert.True(t, c.cleanupCalled, "expected cleanup method was called") 21 | }) 22 | } 23 | 24 | func Test_Cleanup_Listener_Skip_Added(t *testing.T) { 25 | t.Run("Execute Cleanup Handler", func(t *testing.T) { 26 | c := &client{cleanup: true} 27 | 28 | slistener := listener.NewCleanupListener(ctx, target.NewCollection(&target.Target{Client: c})) 29 | slistener(report.LifecycleEvent{Type: report.Added, PolicyReport: preport1}) 30 | 31 | assert.False(t, c.cleanupCalled, "expected cleanup execution was skipped") 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/listener/fixture_test.go: -------------------------------------------------------------------------------- 1 | package listener_test 2 | 3 | import ( 4 | "time" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 10 | "github.com/kyverno/policy-reporter/pkg/fixtures" 11 | ) 12 | 13 | var scopereport1 = &v1alpha2.PolicyReport{ 14 | ObjectMeta: v1.ObjectMeta{ 15 | Name: "polr-test", 16 | Namespace: "test", 17 | CreationTimestamp: v1.NewTime(time.Now().Add(time.Hour)), 18 | }, 19 | Scope: &corev1.ObjectReference{ 20 | APIVersion: "v1", 21 | Kind: "Pod", 22 | Name: "test", 23 | Namespace: "test", 24 | }, 25 | Results: []v1alpha2.PolicyReportResult{fixtures.FailResult}, 26 | Summary: v1alpha2.PolicyReportSummary{Fail: 1}, 27 | } 28 | 29 | var preport1 = &v1alpha2.PolicyReport{ 30 | ObjectMeta: v1.ObjectMeta{ 31 | Name: "polr-test", 32 | Namespace: "test", 33 | CreationTimestamp: v1.Now(), 34 | }, 35 | Results: []v1alpha2.PolicyReportResult{fixtures.FailResult}, 36 | Summary: v1alpha2.PolicyReportSummary{Fail: 1}, 37 | } 38 | 39 | var preport2 = &v1alpha2.PolicyReport{ 40 | ObjectMeta: v1.ObjectMeta{ 41 | Name: "polr-test", 42 | Namespace: "test", 43 | CreationTimestamp: v1.Now(), 44 | }, 45 | Results: []v1alpha2.PolicyReportResult{fixtures.FailPodResult}, 46 | Summary: v1alpha2.PolicyReportSummary{Fail: 1, Pass: 1}, 47 | } 48 | 49 | var preport3 = &v1alpha2.PolicyReport{ 50 | ObjectMeta: v1.ObjectMeta{ 51 | Name: "polr-test", 52 | Namespace: "test", 53 | CreationTimestamp: v1.Now(), 54 | }, 55 | Results: []v1alpha2.PolicyReportResult{}, 56 | } 57 | 58 | var creport = &v1alpha2.ClusterPolicyReport{ 59 | ObjectMeta: v1.ObjectMeta{ 60 | Name: "cpolr-test", 61 | CreationTimestamp: v1.Now(), 62 | }, 63 | Results: []v1alpha2.PolicyReportResult{fixtures.FailResult, fixtures.FailPodResult}, 64 | } 65 | -------------------------------------------------------------------------------- /pkg/listener/metrics/cache.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "strconv" 5 | 6 | gocache "github.com/patrickmn/go-cache" 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/segmentio/fasthash/fnv1a" 9 | 10 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 11 | "github.com/kyverno/policy-reporter/pkg/report" 12 | ) 13 | 14 | type CacheItem struct { 15 | Labels prometheus.Labels 16 | Value float64 17 | } 18 | 19 | type Cache struct { 20 | cache *gocache.Cache 21 | filter *report.ResultFilter 22 | labelGenerator LabelGenerator 23 | } 24 | 25 | func (c *Cache) AddReport(polr v1alpha2.ReportInterface) { 26 | labels := map[string]*CacheItem{} 27 | for _, res := range polr.GetResults() { 28 | if !c.filter.Validate(res) { 29 | continue 30 | } 31 | 32 | l := c.labelGenerator(polr, res) 33 | 34 | hash := labelHash(l) 35 | 36 | item, ok := labels[hash] 37 | if !ok { 38 | labels[hash] = &CacheItem{ 39 | Labels: l, 40 | Value: 1, 41 | } 42 | } else { 43 | item.Value = item.Value + 1 44 | } 45 | } 46 | 47 | list := make([]*CacheItem, 0, len(labels)) 48 | for _, l := range labels { 49 | list = append(list, l) 50 | } 51 | 52 | c.cache.Set(polr.GetID(), list, gocache.NoExpiration) 53 | } 54 | 55 | func (c *Cache) Remove(id string) { 56 | c.cache.Delete(id) 57 | } 58 | 59 | func (c *Cache) GetReportLabels(id string) []*CacheItem { 60 | if item, ok := c.cache.Get(id); ok { 61 | return item.([]*CacheItem) 62 | } 63 | 64 | return []*CacheItem{} 65 | } 66 | 67 | func labelHash(labels prometheus.Labels) string { 68 | h1 := fnv1a.Init64 69 | for i, v := range labels { 70 | h1 = fnv1a.AddString64(h1, i+":"+v) 71 | } 72 | 73 | return strconv.FormatUint(h1, 10) 74 | } 75 | 76 | func NewCache(filter *report.ResultFilter, labelGenerator LabelGenerator) *Cache { 77 | return &Cache{ 78 | cache: gocache.New(gocache.NoExpiration, gocache.NoExpiration), 79 | filter: filter, 80 | labelGenerator: labelGenerator, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pkg/listener/metrics/filter.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 5 | "github.com/kyverno/policy-reporter/pkg/report" 6 | "github.com/kyverno/policy-reporter/pkg/validate" 7 | ) 8 | 9 | func NewResultFilter(namespace, status, policy, source, severity, kind validate.RuleSets) *report.ResultFilter { 10 | f := &report.ResultFilter{} 11 | if namespace.Count() > 0 { 12 | f.AddValidation(func(r v1alpha2.PolicyReportResult) bool { 13 | if !r.HasResource() { 14 | return true 15 | } 16 | 17 | return validate.Namespace(r.GetResource().Namespace, namespace) 18 | }) 19 | } 20 | 21 | if status.Count() > 0 { 22 | f.AddValidation(func(r v1alpha2.PolicyReportResult) bool { 23 | return validate.MatchRuleSet(string(r.Result), status) 24 | }) 25 | } 26 | 27 | if policy.Count() > 0 { 28 | f.AddValidation(func(r v1alpha2.PolicyReportResult) bool { 29 | return validate.MatchRuleSet(r.Policy, policy) 30 | }) 31 | } 32 | 33 | if source.Count() > 0 { 34 | f.AddValidation(func(r v1alpha2.PolicyReportResult) bool { 35 | return validate.MatchRuleSet(r.Source, source) 36 | }) 37 | } 38 | 39 | if severity.Count() > 0 { 40 | f.AddValidation(func(r v1alpha2.PolicyReportResult) bool { 41 | return validate.MatchRuleSet(string(r.Severity), severity) 42 | }) 43 | } 44 | 45 | if kind.Count() > 0 { 46 | f.AddValidation(func(r v1alpha2.PolicyReportResult) bool { 47 | if !r.HasResource() { 48 | return true 49 | } 50 | 51 | return validate.Kind(r.GetResource().Kind, kind) 52 | }) 53 | } 54 | 55 | return f 56 | } 57 | 58 | func NewReportFilter(namespace, source validate.RuleSets) *report.ReportFilter { 59 | f := &report.ReportFilter{} 60 | if namespace.Count() > 0 { 61 | f.AddValidation(func(r v1alpha2.ReportInterface) bool { 62 | return validate.Namespace(r.GetNamespace(), namespace) 63 | }) 64 | } 65 | 66 | if source.Count() > 0 { 67 | f.AddValidation(func(r v1alpha2.ReportInterface) bool { 68 | if len(r.GetResults()) == 0 { 69 | return true 70 | } 71 | 72 | return validate.MatchRuleSet(r.GetResults()[0].Source, source) 73 | }) 74 | } 75 | 76 | return f 77 | } 78 | -------------------------------------------------------------------------------- /pkg/listener/metrics/fixtures_test.go: -------------------------------------------------------------------------------- 1 | package metrics_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | ioprometheusclient "github.com/prometheus/client_model/go" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 10 | ) 11 | 12 | var preport = &v1alpha2.PolicyReport{ 13 | ObjectMeta: v1.ObjectMeta{ 14 | Name: "polr-test", 15 | Namespace: "test", 16 | CreationTimestamp: v1.Now(), 17 | }, 18 | Results: make([]v1alpha2.PolicyReportResult, 0), 19 | Summary: v1alpha2.PolicyReportSummary{}, 20 | } 21 | 22 | func testSummaryMetricLabels( 23 | metric *ioprometheusclient.Metric, 24 | preport v1alpha2.ReportInterface, 25 | status string, 26 | gauge float64, 27 | ) error { 28 | if name := *metric.Label[0].Name; name != "name" { 29 | return fmt.Errorf("unexpected Name Label: %s", name) 30 | } 31 | if value := *metric.Label[0].Value; value != preport.GetName() { 32 | return fmt.Errorf("unexpected Name Label Value: %s", value) 33 | } 34 | 35 | if name := *metric.Label[1].Name; name != "namespace" { 36 | return fmt.Errorf("unexpected Name Label: %s", name) 37 | } 38 | if value := *metric.Label[1].Value; value != preport.GetNamespace() { 39 | return fmt.Errorf("unexpected Namespace Label Value: %s", value) 40 | } 41 | 42 | if name := *metric.Label[2].Name; name != "status" { 43 | return fmt.Errorf("unexpected Name Label: %s", name) 44 | } 45 | if value := *metric.Label[2].Value; value != status { 46 | return fmt.Errorf("unexpected Status Label Value: %s", value) 47 | } 48 | 49 | if value := metric.Gauge.GetValue(); value != gauge { 50 | return fmt.Errorf("unexpected Metric Value: %v", value) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func findMetric(metrics []*ioprometheusclient.MetricFamily, name string) *ioprometheusclient.MetricFamily { 57 | for _, metric := range metrics { 58 | if *metric.Name == name { 59 | return metric 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/listener/scope_results.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 7 | "github.com/kyverno/policy-reporter/pkg/helper" 8 | "github.com/kyverno/policy-reporter/pkg/report" 9 | "github.com/kyverno/policy-reporter/pkg/target" 10 | ) 11 | 12 | const SendScopeResults = "send_scope_results_listener" 13 | 14 | func NewSendScopeResultsListener(targets *target.Collection) report.ScopeResultsListener { 15 | return func(rep v1alpha2.ReportInterface, r []v1alpha2.PolicyReportResult, e bool) { 16 | clients := targets.BatchSendClients() 17 | if len(clients) == 0 { 18 | return 19 | } 20 | 21 | wg := &sync.WaitGroup{} 22 | wg.Add(len(clients)) 23 | 24 | for _, t := range clients { 25 | go func(target target.Client, re v1alpha2.ReportInterface, results []v1alpha2.PolicyReportResult, preExisted bool) { 26 | defer wg.Done() 27 | 28 | filtered := helper.Filter(results, func(result v1alpha2.PolicyReportResult) bool { 29 | return target.Validate(re, result) 30 | }) 31 | 32 | if len(filtered) == 0 || preExisted && target.SkipExistingOnStartup() { 33 | return 34 | } 35 | 36 | target.BatchSend(re, filtered) 37 | }(t, rep, r, e) 38 | } 39 | 40 | wg.Wait() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/listener/scope_results_test.go: -------------------------------------------------------------------------------- 1 | package listener_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 9 | "github.com/kyverno/policy-reporter/pkg/fixtures" 10 | "github.com/kyverno/policy-reporter/pkg/listener" 11 | "github.com/kyverno/policy-reporter/pkg/target" 12 | ) 13 | 14 | func Test_ScopeResultsListener(t *testing.T) { 15 | t.Run("Send Results", func(t *testing.T) { 16 | c := &client{validated: true, batchSend: true} 17 | slistener := listener.NewSendScopeResultsListener(target.NewCollection(&target.Target{Client: c})) 18 | slistener(preport1, []v1alpha2.PolicyReportResult{fixtures.FailResult}, false) 19 | 20 | assert.True(t, c.Called, "Expected Send to be called") 21 | }) 22 | t.Run("Don't Send Result when validation fails", func(t *testing.T) { 23 | c := &client{validated: false, batchSend: true} 24 | slistener := listener.NewSendScopeResultsListener(target.NewCollection(&target.Target{Client: c})) 25 | slistener(preport1, []v1alpha2.PolicyReportResult{fixtures.FailResult}, false) 26 | 27 | assert.False(t, c.Called, "Expected Send not to be called") 28 | }) 29 | t.Run("Don't Send pre existing Result when skipExistingOnStartup is true", func(t *testing.T) { 30 | c := &client{skipExistingOnStartup: true, batchSend: true} 31 | slistener := listener.NewSendScopeResultsListener(target.NewCollection(&target.Target{Client: c})) 32 | slistener(preport1, []v1alpha2.PolicyReportResult{fixtures.FailResult}, true) 33 | 34 | if c.Called { 35 | t.Error("Expected Send not to be called") 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/listener/send_result.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "sync" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 9 | "github.com/kyverno/policy-reporter/pkg/report" 10 | "github.com/kyverno/policy-reporter/pkg/target" 11 | ) 12 | 13 | const SendResults = "send_results_listener" 14 | 15 | func NewSendResultListener(targets *target.Collection) report.PolicyReportResultListener { 16 | return func(rep v1alpha2.ReportInterface, r v1alpha2.PolicyReportResult, e bool) { 17 | clients := targets.SingleSendClients() 18 | if len(clients) == 0 { 19 | return 20 | } 21 | 22 | wg := &sync.WaitGroup{} 23 | wg.Add(len(clients)) 24 | 25 | for _, t := range clients { 26 | go func(target target.Client, re v1alpha2.ReportInterface, result v1alpha2.PolicyReportResult, preExisted bool) { 27 | defer wg.Done() 28 | 29 | if !result.HasResource() && re.GetScope() != nil { 30 | result.Resources = []corev1.ObjectReference{*re.GetScope()} 31 | } 32 | 33 | if (preExisted && target.SkipExistingOnStartup()) || !target.Validate(re, result) { 34 | return 35 | } 36 | 37 | target.Send(result) 38 | }(t, rep, r, e) 39 | } 40 | 41 | wg.Wait() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/listener/store.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "context" 5 | 6 | "go.uber.org/zap" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/report" 9 | ) 10 | 11 | const Store = "store_listener" 12 | 13 | func NewStoreListener(ctx context.Context, store report.PolicyReportStore) report.PolicyReportListener { 14 | return func(event report.LifecycleEvent) { 15 | if event.Type == report.Deleted { 16 | logOnError("remove", event.PolicyReport.GetName(), store.Remove(ctx, event.PolicyReport.GetID())) 17 | return 18 | } 19 | 20 | if event.Type == report.Updated { 21 | logOnError("update", event.PolicyReport.GetName(), store.Update(ctx, event.PolicyReport)) 22 | return 23 | } 24 | 25 | logOnError("add", event.PolicyReport.GetName(), store.Update(ctx, event.PolicyReport)) 26 | } 27 | } 28 | 29 | func logOnError(operation, name string, err error) { 30 | if err != nil { 31 | zap.L().Error("failed to "+operation+" policy report", zap.String("name", name), zap.Error(err)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/listener/store_test.go: -------------------------------------------------------------------------------- 1 | package listener_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/kyverno/policy-reporter/pkg/listener" 8 | "github.com/kyverno/policy-reporter/pkg/report" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | func Test_StoreListener(t *testing.T) { 14 | store := report.NewPolicyReportStore() 15 | 16 | t.Run("Save New Report", func(t *testing.T) { 17 | slistener := listener.NewStoreListener(ctx, store) 18 | slistener(report.LifecycleEvent{Type: report.Added, PolicyReport: preport1}) 19 | 20 | if _, err := store.Get(ctx, preport1.GetID()); err != nil { 21 | t.Error("Expected Report to be stored") 22 | } 23 | }) 24 | t.Run("Update Modified Report", func(t *testing.T) { 25 | slistener := listener.NewStoreListener(ctx, store) 26 | slistener(report.LifecycleEvent{Type: report.Updated, PolicyReport: preport2}) 27 | 28 | if preport, err := store.Get(ctx, preport2.GetID()); err != nil && len(preport.GetResults()) == 2 { 29 | t.Error("Expected Report to be updated") 30 | } 31 | }) 32 | t.Run("Remove Deleted Report", func(t *testing.T) { 33 | slistener := listener.NewStoreListener(ctx, store) 34 | slistener(report.LifecycleEvent{Type: report.Deleted, PolicyReport: preport2}) 35 | 36 | if _, err := store.Get(ctx, preport2.GetID()); err == nil { 37 | t.Error("Expected Report to be removed") 38 | } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/listener/sync_results.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 8 | "github.com/kyverno/policy-reporter/pkg/helper" 9 | "github.com/kyverno/policy-reporter/pkg/report" 10 | "github.com/kyverno/policy-reporter/pkg/target" 11 | ) 12 | 13 | const SendSyncResults = "send_sync_results_listener" 14 | 15 | func NewSendSyncResultsListener(targets *target.Collection) report.SyncResultsListener { 16 | ready := make(chan bool) 17 | ok := false 18 | go func() { 19 | ok = targets.Reset(context.Background()) 20 | if ok { 21 | close(ready) 22 | } 23 | }() 24 | 25 | return func(rep v1alpha2.ReportInterface) { 26 | clients := targets.SyncClients() 27 | if len(clients) == 0 { 28 | return 29 | } 30 | 31 | if !ok { 32 | <-ready 33 | } 34 | 35 | wg := &sync.WaitGroup{} 36 | wg.Add(len(clients)) 37 | 38 | for _, t := range clients { 39 | go func(target target.Client, re v1alpha2.ReportInterface) { 40 | defer wg.Done() 41 | 42 | filtered := helper.Filter(re.GetResults(), func(result v1alpha2.PolicyReportResult) bool { 43 | return target.Validate(re, result) 44 | }) 45 | 46 | target.BatchSend(re, filtered) 47 | }(t, rep) 48 | } 49 | 50 | wg.Wait() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/report/client.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 5 | ) 6 | 7 | // PolicyReportListener is called whenever a new PolicyReport comes in 8 | type PolicyReportListener = func(LifecycleEvent) 9 | 10 | // PolicyReportResultListener is called whenever a new PolicyResult comes in 11 | type PolicyReportResultListener = func(v1alpha2.ReportInterface, v1alpha2.PolicyReportResult, bool) 12 | 13 | // ScopeResultsListener is called whenever a new PolicyReport with a single resource scope and new results comes in 14 | type ScopeResultsListener = func(v1alpha2.ReportInterface, []v1alpha2.PolicyReportResult, bool) 15 | 16 | // SyncResultsListener is called whenever a PolicyReport event comes in 17 | type SyncResultsListener = func(v1alpha2.ReportInterface) 18 | 19 | // PolicyReportClient watches for PolicyReport Events and executes registered callback 20 | type PolicyReportClient interface { 21 | // Run starts the informer and workerqueue 22 | Run(worker int, stopper chan struct{}) error 23 | // Sync Report Informer and start watching for events 24 | Sync(stopper chan struct{}) error 25 | // HasSynced the configured PolicyReport 26 | HasSynced() bool 27 | // Stop the client 28 | Stop() 29 | } 30 | -------------------------------------------------------------------------------- /pkg/report/meta_filter.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/validate" 5 | ) 6 | 7 | type Namespaced interface { 8 | GetNamespace() string 9 | } 10 | 11 | type MetaFilter struct { 12 | disbaleClusterReports bool 13 | namespace validate.RuleSets 14 | } 15 | 16 | func (f *MetaFilter) DisableClusterReports() bool { 17 | return f.disbaleClusterReports 18 | } 19 | 20 | func (f *MetaFilter) AllowReport(report Namespaced) bool { 21 | return validate.Namespace(report.GetNamespace(), f.namespace) 22 | } 23 | 24 | func NewMetaFilter(disableClusterReports bool, namespace validate.RuleSets) *MetaFilter { 25 | return &MetaFilter{disableClusterReports, namespace} 26 | } 27 | -------------------------------------------------------------------------------- /pkg/report/model.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 5 | ) 6 | 7 | // Event Enum 8 | type Event int 9 | 10 | func (e Event) String() string { 11 | switch e { 12 | case Added: 13 | return "add" 14 | case Updated: 15 | return "update" 16 | case Deleted: 17 | return "delete" 18 | } 19 | 20 | return "unknown" 21 | } 22 | 23 | // Possible PolicyReport Event Enums 24 | const ( 25 | Added Event = iota 26 | Updated 27 | Deleted 28 | ) 29 | 30 | // LifecycleEvent of PolicyReports 31 | type LifecycleEvent struct { 32 | Type Event 33 | PolicyReport v1alpha2.ReportInterface 34 | } 35 | 36 | // ResourceType Enum defined for PolicyReport 37 | type ResourceType = string 38 | 39 | // ReportType Enum 40 | const ( 41 | PolicyReportType ResourceType = "PolicyReport" 42 | ClusterPolicyReportType ResourceType = "ClusterPolicyReport" 43 | ) 44 | 45 | func GetType(r v1alpha2.ReportInterface) ResourceType { 46 | if r.GetNamespace() == "" { 47 | return ClusterPolicyReportType 48 | } 49 | 50 | return PolicyReportType 51 | } 52 | 53 | func FindNewResults(nr, or v1alpha2.ReportInterface) []v1alpha2.PolicyReportResult { 54 | if or == nil { 55 | return nr.GetResults() 56 | } 57 | 58 | diff := make([]v1alpha2.PolicyReportResult, 0) 59 | loop: 60 | for _, r := range nr.GetResults() { 61 | for _, o := range or.GetResults() { 62 | if o.GetID() == r.GetID() { 63 | continue loop 64 | } 65 | } 66 | 67 | diff = append(diff, r) 68 | } 69 | 70 | return diff 71 | } 72 | -------------------------------------------------------------------------------- /pkg/report/report_filter.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 5 | ) 6 | 7 | type ReportValidation = func(v1alpha2.ReportInterface) bool 8 | 9 | type ReportFilter struct { 10 | validations []ReportValidation 11 | } 12 | 13 | func (rf *ReportFilter) AddValidation(v ReportValidation) { 14 | rf.validations = append(rf.validations, v) 15 | } 16 | 17 | func (rf *ReportFilter) Validate(report v1alpha2.ReportInterface) bool { 18 | for _, validation := range rf.validations { 19 | if !validation(report) { 20 | return false 21 | } 22 | } 23 | 24 | return true 25 | } 26 | 27 | func NewReportFilter() *ReportFilter { 28 | return &ReportFilter{} 29 | } 30 | -------------------------------------------------------------------------------- /pkg/report/report_filter_test.go: -------------------------------------------------------------------------------- 1 | package report_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 7 | "github.com/kyverno/policy-reporter/pkg/report" 8 | ) 9 | 10 | func Test_ReportFilter(t *testing.T) { 11 | t.Run("don't filter any result without validations", func(t *testing.T) { 12 | filter := report.NewReportFilter() 13 | if !filter.Validate(preport) { 14 | t.Error("Expected result validates to true") 15 | } 16 | }) 17 | t.Run("filter result with a false validation", func(t *testing.T) { 18 | filter := report.NewReportFilter() 19 | filter.AddValidation(func(r v1alpha2.ReportInterface) bool { return false }) 20 | if filter.Validate(preport) { 21 | t.Error("Expected result validates to false") 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/report/result/reconditioner.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 7 | "github.com/kyverno/policy-reporter/pkg/helper" 8 | ) 9 | 10 | type Reconditioner struct { 11 | defaultIDGenerator IDGenerator 12 | customIDGenerators map[string]IDGenerator 13 | } 14 | 15 | func (r *Reconditioner) Prepare(polr v1alpha2.ReportInterface) v1alpha2.ReportInterface { 16 | generator := r.defaultIDGenerator 17 | if g, ok := r.customIDGenerators[strings.ToLower(polr.GetSource())]; ok { 18 | generator = g 19 | } 20 | 21 | results := polr.GetResults() 22 | for i, r := range results { 23 | r.ID = generator.Generate(polr, r) 24 | r.Category = helper.Defaults(r.Category, "Other") 25 | 26 | scope := polr.GetScope() 27 | if len(r.Resources) == 0 && scope != nil { 28 | r.Resources = append(r.Resources, *scope) 29 | } 30 | 31 | results[i] = r 32 | } 33 | 34 | return polr 35 | } 36 | 37 | func NewReconditioner(generators map[string]IDGenerator) *Reconditioner { 38 | return &Reconditioner{ 39 | defaultIDGenerator: NewIDGenerator(nil), 40 | customIDGenerators: generators, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/report/result/resource.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 7 | ) 8 | 9 | func Resource(p v1alpha2.ReportInterface, r v1alpha2.PolicyReportResult) *corev1.ObjectReference { 10 | if r.HasResource() { 11 | return r.GetResource() 12 | } else if p.GetScope() != nil { 13 | return p.GetScope() 14 | } 15 | 16 | return &corev1.ObjectReference{} 17 | } 18 | -------------------------------------------------------------------------------- /pkg/report/result/resource_test.go: -------------------------------------------------------------------------------- 1 | package result_test 2 | 3 | import ( 4 | "testing" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 9 | "github.com/kyverno/policy-reporter/pkg/report/result" 10 | ) 11 | 12 | func TestResource(t *testing.T) { 13 | t.Run("resource from scope", func(t *testing.T) { 14 | resource := &corev1.ObjectReference{Name: "test", Kind: "Pod"} 15 | 16 | res := result.Resource(&v1alpha2.PolicyReport{Scope: resource}, v1alpha2.PolicyReportResult{}) 17 | 18 | if res != resource { 19 | t.Error("expected function to return scope resource") 20 | } 21 | }) 22 | t.Run("resource from result", func(t *testing.T) { 23 | resource := &corev1.ObjectReference{Name: "test", Kind: "Pod"} 24 | 25 | res := result.Resource(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{*resource}}) 26 | 27 | if res.Name != resource.Name { 28 | t.Error("expected function to return result resource") 29 | } 30 | }) 31 | t.Run("empty fallback resource", func(t *testing.T) { 32 | res := result.Resource(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{}}) 33 | 34 | if res == nil { 35 | t.Error("expected function to return empty fallback resource") 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/report/result_filter.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 5 | ) 6 | 7 | type ResultValidation = func(v1alpha2.PolicyReportResult) bool 8 | 9 | type ResultFilter struct { 10 | validations []ResultValidation 11 | Sources []string 12 | MinimumSeverity string 13 | } 14 | 15 | func (rf *ResultFilter) AddValidation(v ResultValidation) { 16 | rf.validations = append(rf.validations, v) 17 | } 18 | 19 | func (rf *ResultFilter) Validate(result v1alpha2.PolicyReportResult) bool { 20 | for _, validation := range rf.validations { 21 | if !validation(result) { 22 | return false 23 | } 24 | } 25 | 26 | return true 27 | } 28 | 29 | func NewResultFilter() *ResultFilter { 30 | return &ResultFilter{} 31 | } 32 | -------------------------------------------------------------------------------- /pkg/report/result_filter_test.go: -------------------------------------------------------------------------------- 1 | package report_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 7 | "github.com/kyverno/policy-reporter/pkg/fixtures" 8 | "github.com/kyverno/policy-reporter/pkg/report" 9 | ) 10 | 11 | func Test_ResultFilter(t *testing.T) { 12 | t.Run("don't filter any result without validations", func(t *testing.T) { 13 | filter := report.NewResultFilter() 14 | if !filter.Validate(fixtures.FailResult) { 15 | t.Error("Expected result validates to true") 16 | } 17 | }) 18 | t.Run("filter result with a false validation", func(t *testing.T) { 19 | filter := report.NewResultFilter() 20 | filter.AddValidation(func(r v1alpha2.PolicyReportResult) bool { return false }) 21 | if filter.Validate(fixtures.FailResult) { 22 | t.Error("Expected result validates to false") 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/target/alertmanager/client.go: -------------------------------------------------------------------------------- 1 | package alertmanager 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/target" 5 | ) 6 | 7 | // Client extends the target.Client interface with AlertManager-specific functionality 8 | type Client interface { 9 | target.Client 10 | } 11 | 12 | // Ensure the client type implements the Client interface 13 | var _ Client = (*client)(nil) 14 | -------------------------------------------------------------------------------- /pkg/target/factory.go: -------------------------------------------------------------------------------- 1 | package target 2 | 3 | import ( 4 | "github.com/kyverno/policy-reporter/pkg/crd/api/targetconfig/v1alpha1" 5 | ) 6 | 7 | type Factory interface { 8 | CreateClients(config *Targets) *Collection 9 | CreateLokiTarget(config, parent *v1alpha1.Config[v1alpha1.LokiOptions]) *Target 10 | CreateSingleClient(*v1alpha1.TargetConfig) (*Target, error) 11 | CreateElasticsearchTarget(config, parent *v1alpha1.Config[v1alpha1.ElasticsearchOptions]) *Target 12 | CreateSlackTarget(config, parent *v1alpha1.Config[v1alpha1.SlackOptions]) *Target 13 | CreateDiscordTarget(config, parent *v1alpha1.Config[v1alpha1.WebhookOptions]) *Target 14 | CreateTeamsTarget(config, parent *v1alpha1.Config[v1alpha1.WebhookOptions]) *Target 15 | CreateWebhookTarget(config, parent *v1alpha1.Config[v1alpha1.WebhookOptions]) *Target 16 | CreateTelegramTarget(config, parent *v1alpha1.Config[v1alpha1.TelegramOptions]) *Target 17 | CreateGoogleChatTarget(config, parent *v1alpha1.Config[v1alpha1.WebhookOptions]) *Target 18 | CreateJiraTarget(config, parent *v1alpha1.Config[v1alpha1.JiraOptions]) *Target 19 | CreateS3Target(config, parent *v1alpha1.Config[v1alpha1.S3Options]) *Target 20 | CreateKinesisTarget(config, parent *v1alpha1.Config[v1alpha1.KinesisOptions]) *Target 21 | CreateSecurityHubTarget(config, parent *v1alpha1.Config[v1alpha1.SecurityHubOptions]) *Target 22 | CreateGCSTarget(config, parent *v1alpha1.Config[v1alpha1.GCSOptions]) *Target 23 | CreateSplunkTarget(config, parent *v1alpha1.Config[v1alpha1.SplunkOptions]) *Target 24 | } 25 | -------------------------------------------------------------------------------- /pkg/target/formatting/resource.go: -------------------------------------------------------------------------------- 1 | package formatting 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | ) 9 | 10 | func ResourceString(res *corev1.ObjectReference) string { 11 | var resource string 12 | if res.Namespace == "" { 13 | resource = fmt.Sprintf("%s/%s: %s", res.APIVersion, res.Kind, res.Name) 14 | } else { 15 | resource = fmt.Sprintf("%s/%s: %s/%s", res.APIVersion, res.Kind, res.Namespace, res.Name) 16 | } 17 | 18 | return strings.Trim(resource, "/") 19 | } 20 | -------------------------------------------------------------------------------- /pkg/target/formatting/resource_test.go: -------------------------------------------------------------------------------- 1 | package formatting_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | corev1 "k8s.io/api/core/v1" 8 | 9 | "github.com/kyverno/policy-reporter/pkg/target/formatting" 10 | ) 11 | 12 | func TestResourceString(t *testing.T) { 13 | t.Run("namespaced resource", func(t *testing.T) { 14 | res := formatting.ResourceString(&corev1.ObjectReference{ 15 | APIVersion: "v1", 16 | Kind: "Deployment", 17 | Name: "nginx", 18 | Namespace: "default", 19 | }) 20 | 21 | assert.Equal(t, "v1/Deployment: default/nginx", res) 22 | }) 23 | 24 | t.Run("cluster resource", func(t *testing.T) { 25 | res := formatting.ResourceString(&corev1.ObjectReference{ 26 | APIVersion: "v1", 27 | Kind: "Namespace", 28 | Name: "default", 29 | }) 30 | 31 | assert.Equal(t, "v1/Namespace: default", res) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/target/gcs/gcs.go: -------------------------------------------------------------------------------- 1 | package gcs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "go.uber.org/zap" 10 | 11 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 12 | "github.com/kyverno/policy-reporter/pkg/target" 13 | "github.com/kyverno/policy-reporter/pkg/target/http" 14 | "github.com/kyverno/policy-reporter/pkg/target/provider/gcs" 15 | ) 16 | 17 | // Options to configure the GCS target 18 | type Options struct { 19 | target.ClientOptions 20 | CustomFields map[string]string 21 | Client gcs.Client 22 | Prefix string 23 | } 24 | 25 | type client struct { 26 | target.BaseClient 27 | customFields map[string]string 28 | client gcs.Client 29 | prefix string 30 | } 31 | 32 | func (c *client) Send(result v1alpha2.PolicyReportResult) { 33 | if len(c.customFields) > 0 { 34 | props := make(map[string]string, 0) 35 | 36 | for property, value := range c.customFields { 37 | props[property] = value 38 | } 39 | 40 | for property, value := range result.Properties { 41 | props[property] = value 42 | } 43 | 44 | result.Properties = props 45 | } 46 | 47 | body := new(bytes.Buffer) 48 | 49 | if err := json.NewEncoder(body).Encode(http.NewJSONResult(result)); err != nil { 50 | zap.L().Error(c.Name()+": encode error", zap.Error(err)) 51 | return 52 | } 53 | t := time.Unix(result.Timestamp.Seconds, int64(result.Timestamp.Nanos)) 54 | key := fmt.Sprintf("%s/%s/%s-%s-%s.json", c.prefix, t.Format("2006-01-02"), result.Policy, result.ID, t.Format(time.RFC3339Nano)) 55 | 56 | err := c.client.Upload(body, key) 57 | if err != nil { 58 | zap.L().Error(c.Name()+": Upload error", zap.Error(err)) 59 | return 60 | } 61 | 62 | zap.L().Info(c.Name() + ": PUSH OK") 63 | } 64 | 65 | func (c *client) Type() target.ClientType { 66 | return target.SingleSend 67 | } 68 | 69 | // NewClient creates a new GCS.client to send Results to Google Cloud Storage. 70 | func NewClient(options Options) target.Client { 71 | return &client{ 72 | target.NewBaseClient(options.ClientOptions), 73 | options.CustomFields, 74 | options.Client, 75 | options.Prefix, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /pkg/target/gcs/gcs_test.go: -------------------------------------------------------------------------------- 1 | package gcs_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/fixtures" 9 | "github.com/kyverno/policy-reporter/pkg/target" 10 | "github.com/kyverno/policy-reporter/pkg/target/gcs" 11 | ) 12 | 13 | type testClient struct { 14 | err error 15 | callback func(body *bytes.Buffer, key string) 16 | } 17 | 18 | func (c *testClient) Upload(_ *bytes.Buffer, _ string) error { 19 | return c.err 20 | } 21 | 22 | var testCallback = func(body *bytes.Buffer, key string) {} 23 | 24 | func Test_GCSTarget(t *testing.T) { 25 | t.Run("Send", func(t *testing.T) { 26 | callback := func(body *bytes.Buffer, key string) { 27 | report := new(bytes.Buffer) 28 | json.NewEncoder(report).Encode(fixtures.CompleteTargetSendResult) 29 | 30 | if body != report { 31 | buf := new(bytes.Buffer) 32 | buf.ReadFrom(body) 33 | 34 | t.Errorf("Unexpected Body Content: %s", buf.String()) 35 | } 36 | } 37 | 38 | client := gcs.NewClient(gcs.Options{ 39 | ClientOptions: target.ClientOptions{ 40 | Name: "GCS", 41 | }, 42 | CustomFields: map[string]string{"cluster": "name"}, 43 | Client: &testClient{nil, callback}, 44 | }) 45 | client.Send(fixtures.CompleteTargetSendResult) 46 | 47 | if len(fixtures.CompleteTargetSendResult.Properties) > 1 || fixtures.CompleteTargetSendResult.Properties["cluster"] != "" { 48 | t.Error("expected customFields are not added to the actuel result") 49 | } 50 | }) 51 | t.Run("Name", func(t *testing.T) { 52 | client := gcs.NewClient(gcs.Options{ 53 | ClientOptions: target.ClientOptions{ 54 | Name: "GCS", 55 | }, 56 | Client: &testClient{}, 57 | }) 58 | 59 | if client.Name() != "GCS" { 60 | t.Errorf("Unexpected Name %s", client.Name()) 61 | } 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/target/http/logroundtripper.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httputil" 7 | 8 | "go.uber.org/zap" 9 | ) 10 | 11 | func NewLoggingRoundTripper(roundTripper http.RoundTripper) http.RoundTripper { 12 | return &logRoundTripper{roundTripper: roundTripper} 13 | } 14 | 15 | type logRoundTripper struct { 16 | roundTripper http.RoundTripper 17 | } 18 | 19 | var _ http.RoundTripper = (*logRoundTripper)(nil) 20 | 21 | func (rt *logRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 22 | logger := zap.L() 23 | if logger.Core().Enabled(zap.DebugLevel) { 24 | if info, err := httputil.DumpRequest(req, true); err == nil { 25 | logger.Debug(fmt.Sprintf("Sending request: %s", string(info))) 26 | } 27 | } 28 | resp, err := rt.roundTripper.RoundTrip(req) 29 | if resp != nil { 30 | if logger.Core().Enabled(zap.DebugLevel) { 31 | if info, err := httputil.DumpResponse(resp, true); err == nil { 32 | logger.Debug(fmt.Sprintf("Received response: %s", string(info))) 33 | } 34 | } 35 | } 36 | return resp, err 37 | } 38 | -------------------------------------------------------------------------------- /pkg/target/http/logroundtripper_test.go: -------------------------------------------------------------------------------- 1 | package http_test 2 | 3 | import ( 4 | net "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "go.uber.org/zap" 10 | "go.uber.org/zap/zaptest/observer" 11 | 12 | "github.com/kyverno/policy-reporter/pkg/target/http" 13 | ) 14 | 15 | type mock struct{} 16 | 17 | func (rt mock) RoundTrip(req *net.Request) (*net.Response, error) { 18 | return httptest.NewRecorder().Result(), nil 19 | } 20 | 21 | func TestDebug(t *testing.T) { 22 | obs, logs := observer.New(zap.DebugLevel) 23 | 24 | zap.ReplaceGlobals(zap.New(obs)) 25 | 26 | r := http.NewLoggingRoundTripper(mock{}) 27 | 28 | _, err := r.RoundTrip(httptest.NewRequest("GET", "http://localhost:8080/healthz", nil)) 29 | 30 | assert.Nil(t, err) 31 | 32 | assert.Equal(t, 2, logs.FilterLevelExact(zap.DebugLevel).Len()) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/target/http/model.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | // Client Interface definition for HTTP based targets 9 | type Client interface { 10 | Do(req *http.Request) (*http.Response, error) 11 | } 12 | 13 | // Resource JSON structure for HTTP Requests 14 | type Resource struct { 15 | APIVersion string `json:"apiVersion"` 16 | Kind string `json:"kind"` 17 | Name string `json:"name"` 18 | Namespace string `json:"namespace,omitempty"` 19 | UID string `json:"uid"` 20 | } 21 | 22 | // Result JSON structure for HTTP Requests 23 | type Result struct { 24 | Message string `json:"message"` 25 | Policy string `json:"policy"` 26 | Rule string `json:"rule"` 27 | Priority string `json:"priority"` 28 | Status string `json:"status"` 29 | Severity string `json:"severity,omitempty"` 30 | Category string `json:"category,omitempty"` 31 | Scored bool `json:"scored"` 32 | Properties map[string]string `json:"properties,omitempty"` 33 | Resource Resource `json:"resource"` 34 | CreationTimestamp time.Time `json:"creationTimestamp"` 35 | Source string `json:"source"` 36 | } 37 | -------------------------------------------------------------------------------- /pkg/target/kinesis/kinesis.go: -------------------------------------------------------------------------------- 1 | package kinesis 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "go.uber.org/zap" 10 | 11 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 12 | "github.com/kyverno/policy-reporter/pkg/target" 13 | "github.com/kyverno/policy-reporter/pkg/target/http" 14 | "github.com/kyverno/policy-reporter/pkg/target/provider/aws" 15 | ) 16 | 17 | // Options to configure the Kinesis target 18 | type Options struct { 19 | target.ClientOptions 20 | CustomFields map[string]string 21 | Kinesis aws.Client 22 | } 23 | 24 | type client struct { 25 | target.BaseClient 26 | customFields map[string]string 27 | kinesis aws.Client 28 | } 29 | 30 | func (c *client) Send(result v1alpha2.PolicyReportResult) { 31 | if len(c.customFields) > 0 { 32 | props := make(map[string]string, 0) 33 | 34 | for property, value := range c.customFields { 35 | props[property] = value 36 | } 37 | 38 | for property, value := range result.Properties { 39 | props[property] = value 40 | } 41 | 42 | result.Properties = props 43 | } 44 | 45 | body := new(bytes.Buffer) 46 | 47 | if err := json.NewEncoder(body).Encode(http.NewJSONResult(result)); err != nil { 48 | zap.L().Error("failed to encode result", zap.String("name", c.Name()), zap.Error(err)) 49 | return 50 | } 51 | t := time.Unix(result.Timestamp.Seconds, int64(result.Timestamp.Nanos)) 52 | key := fmt.Sprintf("%s-%s-%s", result.Policy, result.ID, t.Format(time.RFC3339Nano)) 53 | 54 | if err := c.kinesis.Upload(body, key); err != nil { 55 | zap.L().Error("kinesis upload error", zap.String("name", c.Name()), zap.Error(err)) 56 | return 57 | } 58 | 59 | zap.L().Info("PUSH OK", zap.String("name", c.Name())) 60 | } 61 | 62 | func (c *client) Type() target.ClientType { 63 | return target.SingleSend 64 | } 65 | 66 | // NewClient creates a new Kinesis.client to send Results to AWS Kinesis compatible source 67 | func NewClient(options Options) target.Client { 68 | return &client{ 69 | target.NewBaseClient(options.ClientOptions), 70 | options.CustomFields, 71 | options.Kinesis, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pkg/target/kinesis/kinesis_test.go: -------------------------------------------------------------------------------- 1 | package kinesis_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/fixtures" 9 | "github.com/kyverno/policy-reporter/pkg/target" 10 | "github.com/kyverno/policy-reporter/pkg/target/kinesis" 11 | ) 12 | 13 | type testClient struct { 14 | err error 15 | callback func(body *bytes.Buffer, key string) 16 | } 17 | 18 | func (c *testClient) Upload(_ *bytes.Buffer, _ string) error { 19 | return c.err 20 | } 21 | 22 | var testCallback = func(body *bytes.Buffer, key string) {} 23 | 24 | func Test_KinesisTarget(t *testing.T) { 25 | t.Run("Send", func(t *testing.T) { 26 | callback := func(body *bytes.Buffer, key string) { 27 | report := new(bytes.Buffer) 28 | if err := json.NewEncoder(report).Encode(fixtures.CompleteTargetSendResult); err != nil { 29 | t.Errorf("Failed to encode report message: %s", err) 30 | } 31 | 32 | if body != report { 33 | buf := new(bytes.Buffer) 34 | if _, err := buf.ReadFrom(body); err != nil { 35 | t.Errorf("Failed to read from body: %s", err) 36 | } 37 | 38 | t.Errorf("Unexpected Body Content: %s", buf.String()) 39 | } 40 | } 41 | 42 | client := kinesis.NewClient(kinesis.Options{ 43 | ClientOptions: target.ClientOptions{ 44 | Name: "Kinesis", 45 | }, 46 | CustomFields: map[string]string{"cluster": "name"}, 47 | Kinesis: &testClient{nil, callback}, 48 | }) 49 | client.Send(fixtures.CompleteTargetSendResult) 50 | 51 | if len(fixtures.CompleteTargetSendResult.Properties) > 1 || fixtures.CompleteTargetSendResult.Properties["cluster"] != "" { 52 | t.Error("expected customFields are not added to the actuel result") 53 | } 54 | }) 55 | t.Run("Name", func(t *testing.T) { 56 | client := kinesis.NewClient(kinesis.Options{ 57 | ClientOptions: target.ClientOptions{ 58 | Name: "Kinesis", 59 | }, 60 | Kinesis: &testClient{}, 61 | }) 62 | 63 | if client.Name() != "Kinesis" { 64 | t.Errorf("Unexpected Name %s", client.Name()) 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/target/provider/aws/aws_test.go: -------------------------------------------------------------------------------- 1 | package aws_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/helper" 9 | "github.com/kyverno/policy-reporter/pkg/target/provider/aws" 10 | ) 11 | 12 | func TestS3Client(t *testing.T) { 13 | client := aws.NewS3Client("access", "secret", "eu-central-1", "http://s3.aws.com", "policy-reporter", false, aws.WithKMS(true, helper.ToPointer("kms"), helper.ToPointer("encryption"))) 14 | 15 | assert.NotNil(t, client) 16 | } 17 | 18 | func TestKinesisClient(t *testing.T) { 19 | client := aws.NewKinesisClient("access", "secret", "eu-central-1", "http://kinesis.aws.com", "policy-reporter") 20 | 21 | assert.NotNil(t, client) 22 | } 23 | 24 | func TestSecurityHubClient(t *testing.T) { 25 | client := aws.NewHubClient("access", "secret", "eu-central-1", "http://securityhub.aws.com") 26 | 27 | assert.NotNil(t, client) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/target/provider/gcs/gcs.go: -------------------------------------------------------------------------------- 1 | package gcs 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | 7 | "cloud.google.com/go/storage" 8 | "go.uber.org/zap" 9 | "golang.org/x/oauth2/google" 10 | "google.golang.org/api/option" 11 | 12 | "github.com/kyverno/policy-reporter/pkg/target/http" 13 | ) 14 | 15 | type Client interface { 16 | // Upload given Data the configured AWS storage 17 | Upload(body *bytes.Buffer, key string) error 18 | } 19 | 20 | type client struct { 21 | bucket string 22 | client *storage.Client 23 | } 24 | 25 | func (c *client) Upload(body *bytes.Buffer, key string) error { 26 | object := c.client.Bucket(c.bucket).Object(key) 27 | 28 | writer := object.NewWriter(context.Background()) 29 | defer writer.Close() 30 | 31 | _, err := writer.Write(body.Bytes()) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | return writer.Close() 37 | } 38 | 39 | // NewClient creates a new GCS.client to send Results to GCS Bucket 40 | func NewClient(ctx context.Context, credentials, bucket string) Client { 41 | options := []option.ClientOption{ 42 | option.WithHTTPClient(http.NewClient("", false)), 43 | } 44 | 45 | if credentials != "" { 46 | cred, err := google.CredentialsFromJSON(ctx, []byte(credentials), storage.ScopeReadWrite) 47 | if err != nil { 48 | zap.L().Error("error while creating GCS credentials", zap.Error(err)) 49 | return nil 50 | } 51 | 52 | options = append(options, option.WithCredentials(cred)) 53 | } 54 | 55 | baseClient, err := storage.NewClient(ctx, options...) 56 | if err != nil { 57 | zap.L().Error("error while creating GCS client", zap.Error(err)) 58 | return nil 59 | } 60 | 61 | return &client{ 62 | bucket, 63 | baseClient, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/target/s3/s3.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "go.uber.org/zap" 10 | 11 | "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2" 12 | "github.com/kyverno/policy-reporter/pkg/target" 13 | "github.com/kyverno/policy-reporter/pkg/target/http" 14 | "github.com/kyverno/policy-reporter/pkg/target/provider/aws" 15 | ) 16 | 17 | // Options to configure the S3 target 18 | type Options struct { 19 | target.ClientOptions 20 | CustomFields map[string]string 21 | S3 aws.Client 22 | Prefix string 23 | } 24 | 25 | type client struct { 26 | target.BaseClient 27 | customFields map[string]string 28 | s3 aws.Client 29 | prefix string 30 | } 31 | 32 | func (c *client) Send(result v1alpha2.PolicyReportResult) { 33 | if len(c.customFields) > 0 { 34 | props := make(map[string]string, 0) 35 | 36 | for property, value := range c.customFields { 37 | props[property] = value 38 | } 39 | 40 | for property, value := range result.Properties { 41 | props[property] = value 42 | } 43 | 44 | result.Properties = props 45 | } 46 | 47 | body := new(bytes.Buffer) 48 | 49 | if err := json.NewEncoder(body).Encode(http.NewJSONResult(result)); err != nil { 50 | zap.L().Error(c.Name()+": encode error", zap.Error(err)) 51 | return 52 | } 53 | t := time.Unix(result.Timestamp.Seconds, int64(result.Timestamp.Nanos)) 54 | key := fmt.Sprintf("%s/%s/%s-%s-%s.json", c.prefix, t.Format("2006-01-02"), result.Policy, result.ID, t.Format(time.RFC3339Nano)) 55 | 56 | if err := c.s3.Upload(body, key); err != nil { 57 | zap.L().Error(c.Name()+": S3 Upload error", zap.Error(err)) 58 | return 59 | } 60 | 61 | zap.L().Info(c.Name() + ": PUSH OK") 62 | } 63 | 64 | func (c *client) Type() target.ClientType { 65 | return target.SingleSend 66 | } 67 | 68 | // NewClient creates a new S3.client to send Results to S3. 69 | func NewClient(options Options) target.Client { 70 | return &client{ 71 | target.NewBaseClient(options.ClientOptions), 72 | options.CustomFields, 73 | options.S3, 74 | options.Prefix, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pkg/target/s3/s3_test.go: -------------------------------------------------------------------------------- 1 | package s3_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/fixtures" 9 | "github.com/kyverno/policy-reporter/pkg/target" 10 | "github.com/kyverno/policy-reporter/pkg/target/s3" 11 | ) 12 | 13 | type testClient struct { 14 | err error 15 | callback func(body *bytes.Buffer, key string) 16 | } 17 | 18 | func (c *testClient) Upload(_ *bytes.Buffer, _ string) error { 19 | return c.err 20 | } 21 | 22 | var testCallback = func(body *bytes.Buffer, key string) {} 23 | 24 | func Test_S3Target(t *testing.T) { 25 | t.Run("Send", func(t *testing.T) { 26 | callback := func(body *bytes.Buffer, key string) { 27 | report := new(bytes.Buffer) 28 | json.NewEncoder(report).Encode(fixtures.CompleteTargetSendResult) 29 | 30 | if body != report { 31 | buf := new(bytes.Buffer) 32 | buf.ReadFrom(body) 33 | 34 | t.Errorf("Unexpected Body Content: %s", buf.String()) 35 | } 36 | } 37 | 38 | client := s3.NewClient(s3.Options{ 39 | ClientOptions: target.ClientOptions{ 40 | Name: "S3", 41 | }, 42 | CustomFields: map[string]string{"cluster": "name"}, 43 | S3: &testClient{nil, callback}, 44 | }) 45 | client.Send(fixtures.CompleteTargetSendResult) 46 | 47 | if len(fixtures.CompleteTargetSendResult.Properties) > 1 || fixtures.CompleteTargetSendResult.Properties["cluster"] != "" { 48 | t.Error("expected customFields are not added to the actuel result") 49 | } 50 | }) 51 | t.Run("Name", func(t *testing.T) { 52 | client := s3.NewClient(s3.Options{ 53 | ClientOptions: target.ClientOptions{ 54 | Name: "S3", 55 | }, 56 | S3: &testClient{}, 57 | }) 58 | 59 | if client.Name() != "S3" { 60 | t.Errorf("Unexpected Name %s", client.Name()) 61 | } 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/target/splunk/splunk_test.go: -------------------------------------------------------------------------------- 1 | package splunk 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/kyverno/policy-reporter/pkg/fixtures" 8 | "github.com/kyverno/policy-reporter/pkg/target" 9 | ) 10 | 11 | type testClient struct { 12 | callBack func(req *http.Request) error 13 | statusCode int 14 | } 15 | 16 | func (c testClient) Do(req *http.Request) (*http.Response, error) { 17 | err := c.callBack(req) 18 | return &http.Response{ 19 | StatusCode: c.statusCode, 20 | }, err 21 | } 22 | 23 | func TestSplunkTarget(t *testing.T) { 24 | t.Run("Send", func(t *testing.T) { 25 | callback := func(req *http.Request) error { 26 | if agent := req.Header.Get("User-Agent"); agent != "Policy-Reporter" { 27 | t.Errorf("Unexpected Agent: %s", agent) 28 | } 29 | 30 | if url := req.URL.String(); url != "http://localhost:8088/services/collector" { 31 | t.Errorf("Unexpected Host: %s", url) 32 | } 33 | 34 | if value := req.Header.Get("Authorization"); value != "Splunk my-token" { 35 | t.Errorf("Unexpected Header Authorization: %s", value) 36 | } 37 | 38 | return nil 39 | } 40 | 41 | client := NewClient(Options{ 42 | ClientOptions: target.ClientOptions{ 43 | Name: "Test", 44 | }, 45 | Host: "http://localhost:8088/services/collector", 46 | Token: "my-token", 47 | Headers: map[string]string{"Authorization": "Splunk my-token"}, 48 | HTTPClient: testClient{callback, 200}, 49 | }) 50 | client.Send(fixtures.CompleteTargetSendResult) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/target/teams/card.go: -------------------------------------------------------------------------------- 1 | package teams 2 | 3 | import ( 4 | "github.com/atc0005/go-teams-notify/v2/adaptivecard" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/helper" 7 | ) 8 | 9 | func newFactSet() adaptivecard.Element { 10 | factSet := adaptivecard.Element{ 11 | Type: adaptivecard.TypeElementFactSet, 12 | } 13 | 14 | return factSet 15 | } 16 | 17 | func newFactSetPointer() *adaptivecard.Element { 18 | factSet := newFactSet() 19 | 20 | return &factSet 21 | } 22 | 23 | func newSubTitle(title string) adaptivecard.Element { 24 | text := adaptivecard.NewTextBlock(title, true) 25 | text.Weight = adaptivecard.WeightBolder 26 | text.IsSubtle = true 27 | 28 | return text 29 | } 30 | 31 | func MapToColumnSet(list map[string]string) adaptivecard.Element { 32 | i := 0 33 | 34 | first := adaptivecard.NewColumn() 35 | first.Items = append(first.Items, newFactSetPointer()) 36 | 37 | second := adaptivecard.NewColumn() 38 | second.Items = append(second.Items, newFactSetPointer()) 39 | 40 | propBlock := adaptivecard.NewColumnSet() 41 | propBlock.Columns = []adaptivecard.Column{first, second} 42 | 43 | for property, value := range list { 44 | index := i % 2 45 | 46 | propBlock.Columns[index].Items[0].Facts = append(propBlock.Columns[index].Items[0].Facts, adaptivecard.Fact{ 47 | Title: helper.Title(property), 48 | Value: value, 49 | }) 50 | 51 | i++ 52 | } 53 | 54 | return propBlock 55 | } 56 | -------------------------------------------------------------------------------- /pkg/validate/model.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | type RuleSets struct { 4 | Exclude []string 5 | Include []string 6 | Selector map[string]string 7 | } 8 | 9 | func (r RuleSets) Count() int { 10 | return len(r.Exclude) + len(r.Include) 11 | } 12 | 13 | func (r RuleSets) Enabled() bool { 14 | return r.Count() > 0 15 | } 16 | -------------------------------------------------------------------------------- /pkg/validate/model_test.go: -------------------------------------------------------------------------------- 1 | package validate_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/kyverno/policy-reporter/pkg/validate" 9 | ) 10 | 11 | func TestCount(t *testing.T) { 12 | t.Run("count include rules", func(t *testing.T) { 13 | assert.Equal(t, 0, validate.RuleSets{}.Count()) 14 | assert.Equal(t, 2, validate.RuleSets{Include: []string{"kyverno", "falco"}}.Count()) 15 | }) 16 | t.Run("count exclude rules", func(t *testing.T) { 17 | assert.Equal(t, 2, validate.RuleSets{Exclude: []string{"kyverno", "falco"}}.Count()) 18 | }) 19 | } 20 | 21 | func TestEnabled(t *testing.T) { 22 | t.Run("enabled when include rule exist", func(t *testing.T) { 23 | assert.False(t, validate.RuleSets{}.Enabled()) 24 | assert.True(t, validate.RuleSets{Include: []string{"kyverno"}}.Enabled()) 25 | }) 26 | t.Run("enabled when exclude rule exist", func(t *testing.T) { 27 | assert.True(t, validate.RuleSets{Exclude: []string{"kyverno", "falco"}}.Enabled()) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/validate/validate.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "github.com/kyverno/go-wildcard" 5 | 6 | "github.com/kyverno/policy-reporter/pkg/helper" 7 | ) 8 | 9 | func Namespace(namespace string, namespaces RuleSets) bool { 10 | if namespace == "" { 11 | return true 12 | } 13 | 14 | return MatchRuleSet(namespace, namespaces) 15 | } 16 | 17 | func Kind(kind string, kinds RuleSets) bool { 18 | if kind == "" { 19 | return true 20 | } 21 | 22 | return MatchRuleSet(kind, kinds) 23 | } 24 | 25 | func MatchRuleSet(value string, rules RuleSets) bool { 26 | if len(rules.Include) > 0 { 27 | for _, ns := range rules.Include { 28 | if wildcard.Match(ns, value) { 29 | return true 30 | } 31 | } 32 | 33 | return false 34 | } else if len(rules.Exclude) > 0 { 35 | for _, ns := range rules.Exclude { 36 | if wildcard.Match(ns, value) { 37 | return false 38 | } 39 | } 40 | } 41 | 42 | return true 43 | } 44 | 45 | func ContainsRuleSet(value string, rules RuleSets) bool { 46 | if len(rules.Include) > 0 { 47 | return helper.Contains(value, rules.Include) 48 | } else if len(rules.Exclude) > 0 && helper.Contains(value, rules.Exclude) { 49 | return false 50 | } 51 | 52 | return true 53 | } 54 | -------------------------------------------------------------------------------- /policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: require-labels 5 | spec: 6 | validationFailureAction: Audit 7 | rules: 8 | - name: check-for-labels 9 | match: 10 | any: 11 | - resources: 12 | kinds: 13 | - Pod 14 | namespaces: 15 | - policy-reporter 16 | 17 | validate: 18 | message: "label 'app.kubernetes.io/test' is required" 19 | pattern: 20 | metadata: 21 | labels: 22 | app.kubernetes.io/test: "?*" -------------------------------------------------------------------------------- /scripts/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /scripts/kind.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | kubeadmConfigPatches: 4 | - |- 5 | kind: ClusterConfiguration 6 | controllerManager: 7 | extraArgs: 8 | bind-address: 0.0.0.0 9 | etcd: 10 | local: 11 | extraArgs: 12 | listen-metrics-urls: http://0.0.0.0:2382 13 | scheduler: 14 | extraArgs: 15 | bind-address: 0.0.0.0 16 | - |- 17 | kind: KubeProxyConfiguration 18 | metricsBindAddress: 0.0.0.0 19 | nodes: 20 | - role: control-plane 21 | kubeadmConfigPatches: 22 | - |- 23 | kind: InitConfiguration 24 | nodeRegistration: 25 | kubeletExtraArgs: 26 | node-labels: "ingress-ready=true" 27 | extraPortMappings: 28 | - containerPort: 80 29 | hostPort: 80 30 | protocol: TCP 31 | - containerPort: 443 32 | hostPort: 443 33 | protocol: TCP 34 | - role: worker 35 | -------------------------------------------------------------------------------- /tc.yaml: -------------------------------------------------------------------------------- 1 | kind: TargetConfig 2 | apiVersion: policyreporter.kyverno.io/v1alpha1 3 | metadata: 4 | name: slack-notifier 5 | spec: 6 | slack: 7 | webhook: "" 8 | channel: "kyverno" 9 | secretRef: "webhook-secret" 10 | filter: 11 | namespaces: 12 | exclude: [trivy-system] 13 | sources: 14 | include: [kyverno] -------------------------------------------------------------------------------- /test/alertmanager/README.md: -------------------------------------------------------------------------------- 1 | # AlertManager Target Test 2 | 3 | This is a simple test program to verify the AlertManager target integration. 4 | 5 | ## Prerequisites 6 | 7 | - Running AlertManager instance (default: http://localhost:9093) 8 | - Go environment 9 | 10 | ## How to run 11 | 12 | ```bash 13 | # From the project root 14 | cd test/alertmanager 15 | go run main.go 16 | ``` 17 | 18 | ## Expected results 19 | 20 | 1. The test sends two types of alerts to the AlertManager: 21 | - A single test alert with policy "test-policy" 22 | - Two batch alerts with policies "batch-policy-1" and "batch-policy-2" 23 | 24 | 2. Check the AlertManager UI to verify the alerts are received with: 25 | - Correct labels (severity, status, source, policy, rule) 26 | - Correct annotations (message, category, custom properties) 27 | - Custom fields (environment: test, test: true) 28 | 29 | ## Troubleshooting 30 | 31 | - If AlertManager is running on a different URL, edit `alertManagerURL` in main.go 32 | - Ensure the AlertManager API endpoint is accessible 33 | - Check AlertManager logs for any errors --------------------------------------------------------------------------------