├── netcheck ├── __init__.py ├── checks │ ├── __init__.py │ ├── internal.py │ ├── http.py │ └── dns.py ├── version.py ├── validation.py └── context.py ├── operator ├── tests │ ├── __init__.py │ ├── testdata │ │ ├── some-config-map-bar.yaml │ │ ├── some-config-map-foo.yaml │ │ ├── delayed-http.yaml │ │ ├── internal-config-map-check.yaml │ │ ├── should-fail.yaml │ │ ├── should-pass.yaml │ │ ├── http-job.yaml │ │ ├── with-service-account.yaml │ │ ├── with-configmap-data.yaml │ │ ├── with-secret-data.yaml │ │ ├── with-context-data.yaml │ │ ├── with-verbose-context.yaml │ │ ├── cluster-dns.yaml │ │ └── with-configmap-json-yaml-data.yaml │ ├── test_verbose_context_data_default_cluster.py │ ├── test_inline_context_data_default_cluster.py │ ├── test_external_secret_data_default_cluster.py │ ├── test_dns_default_cluster.py │ ├── test_dns_cilium_controls.py │ └── conftest.py ├── netchecks_operator │ ├── __init__.py │ └── config.py ├── doc │ └── High-Level-Diagram.png ├── examples │ ├── kind-installation │ │ ├── config.json │ │ └── values.yaml │ ├── policy-reporter │ │ ├── slack-alert.png │ │ ├── prometheus-metrics-screenshot.png │ │ ├── README.md │ │ └── prometheus-values.yaml │ ├── k8s-version.yaml │ ├── cilium-cluster-wide-dns-restrictions │ │ ├── default-dns-netpol.yaml │ │ ├── README.md │ │ ├── cluster-dns-should-work.yaml │ │ ├── kube-dns-netpol.yaml │ │ └── dns-restrictions-assertion.yaml │ ├── value-in-configmap.yaml │ ├── validation │ │ ├── dns.yaml │ │ ├── http-configmap.yaml │ │ └── http.yaml │ ├── cilium-dns-restrictions │ │ ├── dns-netpol.yaml │ │ ├── README.md │ │ ├── dns-restrictions-assertion.yaml │ │ └── cluster-dns-should-work.yaml │ ├── httpbin │ │ ├── http.yaml │ │ └── httpbin.yaml │ ├── default-k8s │ │ ├── http.yaml │ │ └── dns.yaml │ └── meta │ │ └── assert-policyreports-api.yaml ├── .dockerignore ├── charts │ └── netchecks │ │ ├── templates │ │ ├── NOTES.txt │ │ ├── serviceaccount.yaml │ │ ├── service.yaml │ │ ├── clusterrolebinding.yaml │ │ ├── netpol.yaml │ │ ├── configmap.yaml │ │ ├── _helpers.tpl │ │ ├── clusterrole.yaml │ │ └── deployment.yaml │ │ ├── artifacthub-repo.yml │ │ ├── .helmignore │ │ ├── README.md │ │ ├── crds │ │ └── networkassertions.yaml │ │ ├── values.yaml │ │ └── Chart.yaml ├── create-static-manifests.sh ├── pyproject.toml ├── Dockerfile └── README.md ├── tests ├── testdata │ ├── dir-of-data │ │ └── API_TOKEN │ ├── data.json │ ├── dns-config.json │ ├── internal-check-config.json │ ├── dns-config-custom-validation.json │ ├── invalid-config-unknown-check.json │ ├── http-with-headers.json │ ├── config-with-context.json │ ├── config-with-multiple-rules.json │ ├── simple-config-with-expected-failures.json │ ├── simple-config-with-unexpected-failures.json │ └── simple-config.json └── conftest.py ├── docs ├── .eslintrc.json ├── public │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── fonts │ │ ├── lexend.woff2 │ │ ├── Inter-roman.var.woff2 │ │ ├── Inter-italic.var.woff2 │ │ └── lexend.txt │ ├── mstile-150x150.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-precomposed.png │ ├── images │ │ ├── alerting │ │ │ ├── grafana_alerts.png │ │ │ └── grafana_prometheus_dashboard.png │ │ └── architecture │ │ │ └── Netcheck-High-Level-Lifecycle.png │ ├── apple-touch-icon-120x120-precomposed.png │ ├── apple-touch-icon-152x152-precomposed.png │ ├── apple-touch-icon-180x180-precomposed.png │ ├── apple-touch-icon-60x60-precomposed.png │ ├── apple-touch-icon-76x76-precomposed.png │ ├── browserconfig.xml │ ├── site.webmanifest │ └── safari-pinned-tab.svg ├── src │ ├── images │ │ ├── blur-cyan.png │ │ └── blur-indigo.png │ ├── styles │ │ ├── tailwind.css │ │ ├── fonts.css │ │ └── prism.css │ ├── pages │ │ ├── docs │ │ │ ├── testing.md │ │ │ ├── development.md │ │ │ ├── design-principles.md │ │ │ ├── releasing.md │ │ │ ├── installation.md │ │ │ ├── how-to-contribute.md │ │ │ ├── architecture-guide.md │ │ │ ├── writing-plugins.md │ │ │ ├── roadmap.md │ │ │ ├── core-concepts.md │ │ │ ├── k8s_data.md │ │ │ ├── http.md │ │ │ └── alerting.md │ │ ├── _document.jsx │ │ ├── _app.jsx │ │ └── index.md │ └── components │ │ ├── Button.jsx │ │ ├── Logo.jsx │ │ ├── icons │ │ ├── InstallationIcon.jsx │ │ ├── WarningIcon.jsx │ │ ├── ThemingIcon.jsx │ │ ├── PresetsIcon.jsx │ │ ├── PluginsIcon.jsx │ │ └── LightbulbIcon.jsx │ │ ├── QuickLinks.jsx │ │ ├── Prose.jsx │ │ ├── Navigation.jsx │ │ ├── Callout.jsx │ │ ├── Icon.jsx │ │ ├── MobileNavigation.jsx │ │ ├── Search.jsx │ │ └── Fence.jsx ├── jsconfig.json ├── prettier.config.js ├── postcss.config.js ├── next.config.js ├── .gitignore ├── markdoc │ ├── nodes.js │ └── tags.js ├── package.json ├── tailwind.config.js └── README.md ├── .github ├── logo.png ├── chart-values-override.yaml ├── workflows │ ├── helm-chart.yaml │ ├── helm-chart-release.yaml │ └── lint.yaml └── dependabot.yml ├── .gitignore ├── .dockerignore ├── .typos.toml ├── SECURITY.md ├── example-config.json ├── pyproject.toml ├── Dockerfile └── scripts └── check_versions.py /netcheck/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netcheck/checks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operator/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /operator/netchecks_operator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/dir-of-data/API_TOKEN: -------------------------------------------------------------------------------- 1 | api-secret-data -------------------------------------------------------------------------------- /tests/testdata/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "very secret value" 3 | } -------------------------------------------------------------------------------- /docs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/.github/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | netcheck/__pycache__/ 3 | 4 | poetry.lock 5 | 6 | .idea 7 | .aider* 8 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/favicon-16x16.png -------------------------------------------------------------------------------- /docs/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/src/images/blur-cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/src/images/blur-cyan.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/public/fonts/lexend.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/fonts/lexend.woff2 -------------------------------------------------------------------------------- /docs/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/mstile-150x150.png -------------------------------------------------------------------------------- /docs/src/images/blur-indigo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/src/images/blur-indigo.png -------------------------------------------------------------------------------- /operator/doc/High-Level-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/operator/doc/High-Level-Diagram.png -------------------------------------------------------------------------------- /.github/chart-values-override.yaml: -------------------------------------------------------------------------------- 1 | operator: 2 | image: 3 | tag: main 4 | 5 | probeConfig: 6 | image: 7 | tag: main 8 | -------------------------------------------------------------------------------- /docs/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /docs/public/fonts/Inter-roman.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/fonts/Inter-roman.var.woff2 -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /docs/public/fonts/Inter-italic.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/fonts/Inter-italic.var.woff2 -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /operator/examples/kind-installation/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "probe": { 3 | "image": { 4 | "pullPolicy": "Always" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /docs/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/public/images/alerting/grafana_alerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/images/alerting/grafana_alerts.png -------------------------------------------------------------------------------- /docs/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | plugins: [require('prettier-plugin-tailwindcss')], 5 | } 6 | -------------------------------------------------------------------------------- /operator/examples/policy-reporter/slack-alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/operator/examples/policy-reporter/slack-alert.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-120x120-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-120x120-precomposed.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-152x152-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-152x152-precomposed.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-180x180-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-180x180-precomposed.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-60x60-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-60x60-precomposed.png -------------------------------------------------------------------------------- /docs/public/apple-touch-icon-76x76-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/apple-touch-icon-76x76-precomposed.png -------------------------------------------------------------------------------- /operator/.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | .git 3 | __pycache__ 4 | *.pyc 5 | *.pyo 6 | *.pyd 7 | .Python 8 | .pytest_cache 9 | .ruff_cache 10 | .hypothesis 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | .git 3 | __pycache__ 4 | *.pyc 5 | *.pyo 6 | *.pyd 7 | .Python 8 | .pytest_cache 9 | .ruff_cache 10 | .hypothesis 11 | operator 12 | docs 13 | -------------------------------------------------------------------------------- /docs/public/images/alerting/grafana_prometheus_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/images/alerting/grafana_prometheus_dashboard.png -------------------------------------------------------------------------------- /tests/testdata/dns-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "default-dns", "rules": [ 4 | {"type": "dns", "host": "github.com"} 5 | ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /docs/public/images/architecture/Netcheck-High-Level-Lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/docs/public/images/architecture/Netcheck-High-Level-Lifecycle.png -------------------------------------------------------------------------------- /operator/examples/policy-reporter/prometheus-metrics-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardbyte/netchecks/HEAD/operator/examples/policy-reporter/prometheus-metrics-screenshot.png -------------------------------------------------------------------------------- /operator/charts/netchecks/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | Thank you for installing {{ .Chart.Name | upper }}. 3 | 4 | Your release is named {{ .Release.Name }}. 5 | 6 | https://docs.netchecks.io 7 | -------------------------------------------------------------------------------- /operator/tests/testdata/some-config-map-bar.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: some-config-map 5 | data: 6 | foo: "bar" 7 | nameservers: | 8 | ["1.1.1.1", "8.8.8.8"] 9 | -------------------------------------------------------------------------------- /operator/tests/testdata/some-config-map-foo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: some-config-map 5 | data: 6 | foo: "foo" 7 | nameservers: | 8 | ["1.1.1.1", "8.8.8.8"] 9 | -------------------------------------------------------------------------------- /operator/charts/netchecks/artifacthub-repo.yml: -------------------------------------------------------------------------------- 1 | # Artifact Hub repository metadata file 2 | repositoryID: dad484b6-478f-4353-8420-3537a1bc11bd 3 | owners: 4 | - name: Brian Thorne 5 | email: brian@hardbyte.nz 6 | -------------------------------------------------------------------------------- /docs/src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import './fonts.css'; 3 | @import './docsearch.css'; 4 | @import './prism.css'; 5 | @import 'tailwindcss/components'; 6 | @import 'tailwindcss/utilities'; 7 | -------------------------------------------------------------------------------- /tests/testdata/internal-check-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "default", "rules": [{"type": "internal"}] }, 4 | {"name": "default", "rules": [{"type": "internal", "validation": "false"}] } 5 | ] 6 | } -------------------------------------------------------------------------------- /docs/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #da532c 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | tailwindcss: {}, 5 | 'postcss-focus-visible': { 6 | replaceWith: '[data-focus-visible-added]', 7 | }, 8 | autoprefixer: {}, 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /netcheck/version.py: -------------------------------------------------------------------------------- 1 | from importlib import metadata 2 | 3 | try: 4 | NETCHECK_VERSION = metadata.version("netcheck") 5 | except metadata.PackageNotFoundError: 6 | NETCHECK_VERSION = "unknown" 7 | 8 | OUTPUT_JSON_VERSION = "dev" # set to v1 once stable 9 | -------------------------------------------------------------------------------- /tests/testdata/dns-config-custom-validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "default-dns", "rules": [ 4 | {"type": "dns", "host": "github.com", "validation": "data.canonical_name == 'github.com.' && data.response.contains('NOERROR') && size(data.A)>=1"} 5 | ] 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /tests/testdata/invalid-config-unknown-check.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "cloudflare-dns", "rules": [{"type": "blarg", "server": "1.1.1.1", "host": "github.com", "expected": "pass"}] }, 4 | {"name": "github-status", "rules": [{"type": "http", "url": "https://github.com/status", "expected": "pass"}] } 5 | ] 6 | } -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | locale = "en-au" 3 | 4 | [default.extend-identifiers] 5 | center = "center" # Due to CSS usage 6 | authorization = "authorization" # due to rbac.authorization.k8s.io usage 7 | 8 | [files] 9 | ignore-vcs = true 10 | extend-exclude = [ 11 | ".git", 12 | "LICENSE", 13 | "*policyreports*.yaml", 14 | "clusterpolicyreports.yaml" 15 | ] 16 | 17 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | 6 | | Version | Supported | 7 | |---------| ------------------ | 8 | | <= 0.4 | :x: | 9 | | 0.5.x | :white_check_mark: | 10 | 11 | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | To report a security vulnerability in netchecks email Brian Thorne via hardbyte at gmail dot com 16 | -------------------------------------------------------------------------------- /operator/examples/kind-installation/values.yaml: -------------------------------------------------------------------------------- 1 | 2 | operator: 3 | image: 4 | tag: main 5 | 6 | probeConfig: 7 | image: 8 | tag: main 9 | 10 | 11 | policy-reporter: 12 | enabled: true 13 | 14 | metrics: 15 | enabled: true 16 | 17 | ui: 18 | enabled: true 19 | 20 | livenessProbe: null 21 | readinessProbe: null 22 | 23 | crds: 24 | install: true -------------------------------------------------------------------------------- /tests/testdata/http-with-headers.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "get-with-header", "rules": [ 4 | {"type": "http", "url": "https://pie.dev/headers", "headers": {"X-Test-Header": "value"}}, 5 | {"type": "http", "url": "https://pie.dev/headers", "headers": {"X-Header": "secret"}, 6 | "validation": "parse_json(data.body).headers['X-Header'] == 'secret'" } 7 | ] 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | const withMarkdoc = require('@markdoc/next.js') 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | output: 'export', 6 | distDir: 'out', 7 | reactStrictMode: true, 8 | pageExtensions: ['js', 'jsx', 'md'], 9 | images: { 10 | unoptimized: true 11 | }, 12 | experimental: { 13 | scrollRestoration: true, 14 | }, 15 | } 16 | 17 | module.exports = withMarkdoc()(nextConfig) 18 | -------------------------------------------------------------------------------- /operator/charts/netchecks/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.operator.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "netchecks.serviceAccountName" . }} 6 | labels: 7 | {{- include "netchecks.labels" . | nindent 4 }} 8 | {{- with .Values.operator.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /operator/tests/testdata/delayed-http.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: slow-http-request 5 | annotations: 6 | description: Assert pod can connect to a slow http server 7 | spec: 8 | rules: 9 | - name: kubernetes-version 10 | type: http 11 | url: https://pie.dev/delay/5 12 | expected: pass 13 | validate: 14 | message: Http request to delayed API should succeed. 15 | 16 | -------------------------------------------------------------------------------- /tests/testdata/config-with-context.json: -------------------------------------------------------------------------------- 1 | { 2 | "contexts": [ 3 | {"name": "somecontext", "type": "inline", "data": {"key": "secret"}} 4 | ], 5 | "assertions": [ 6 | {"name": "header-with-context-works", "rules": [ 7 | { "type": "http", 8 | "url": "https://pie.dev/headers", 9 | "headers": {"X-Header": "{{ somecontext.key }}"}, 10 | "validation": "parse_json(data.body).headers['X-Header'] == 'secret'"} 11 | ]} 12 | ] 13 | } -------------------------------------------------------------------------------- /operator/charts/netchecks/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /docs/src/pages/docs/testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing 3 | description: Netchecks testing on GitHub Actions and locally. 4 | --- 5 | 6 | Both the Netchecks command line tool and Kubernetes operator have comprehensive test suites that run on GitHub Actions after every commit. The GitHub Actions workflows for testing can be found [here](https://github.com/hardbyte/netchecks/tree/main/.github/workflows). 7 | 8 | 9 | ## Testing the Netchecks Python Library 10 | 11 | ```bash 12 | uv run pytest 13 | ``` -------------------------------------------------------------------------------- /operator/charts/netchecks/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "netchecks.fullname" . }} 5 | labels: 6 | {{- include "netchecks.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.operator.service.type }} 9 | ports: 10 | - port: {{ .Values.operator.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "netchecks.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /docs/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff" 18 | } 19 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /docs/src/pages/docs/development.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Operator Development 3 | description: Running during development 4 | --- 5 | 6 | 7 | ## Start a test cluster 8 | 9 | If using [kind](https://kind.sigs.k8s.io/) 10 | 11 | ```shell 12 | kind create cluster 13 | kubectl config use-context kind-kind 14 | ``` 15 | 16 | ## Install the operator 17 | 18 | To install the operator from the repository 19 | 20 | ```bash 21 | helm upgrade --install netchecks-operator operator/charts/netchecks/ -n netchecks --create-namespace 22 | ``` -------------------------------------------------------------------------------- /operator/charts/netchecks/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: {{ include "netchecks.fullname" . }} 5 | labels: 6 | {{- include "netchecks.labels" . | nindent 4 }} 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: ClusterRole 10 | name: {{ include "netchecks.fullname" . }} 11 | subjects: 12 | - kind: ServiceAccount 13 | name: {{ include "netchecks.serviceAccountName" . }} 14 | namespace: '{{ .Release.Namespace }}' 15 | -------------------------------------------------------------------------------- /netcheck/checks/internal.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | def internal_check( 5 | timeout=5, 6 | ): 7 | """An internal no-op check""" 8 | 9 | test_spec = { 10 | "type": "internal", 11 | "timeout": timeout, 12 | } 13 | 14 | result_data = { 15 | "startTimestamp": datetime.datetime.now(datetime.UTC).isoformat(), 16 | } 17 | 18 | output = {"spec": test_spec, "data": result_data} 19 | 20 | result_data["endTimestamp"] = datetime.datetime.now(datetime.UTC).isoformat() 21 | 22 | return output 23 | -------------------------------------------------------------------------------- /operator/charts/netchecks/templates/netpol.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.networkPolicy.enabled }} 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | name: {{ include "netchecks.fullname" . }} 6 | labels: 7 | {{- include "netchecks.labels" . | nindent 4 }} 8 | spec: 9 | podSelector: 10 | matchLabels: 11 | {{- include "netchecks.selectorLabels" . | nindent 6 }} 12 | policyTypes: 13 | - Egress 14 | 15 | egress: 16 | # Allow operator to talk to anything. Would be better to restrict to K8s API 17 | - {} 18 | {{- end }} -------------------------------------------------------------------------------- /docs/markdoc/nodes.js: -------------------------------------------------------------------------------- 1 | import { Fence } from '@/components/Fence' 2 | import { nodes as defaultNodes } from '@markdoc/markdoc' 3 | 4 | const nodes = { 5 | document: { 6 | render: undefined, 7 | }, 8 | th: { 9 | ...defaultNodes.th, 10 | attributes: { 11 | ...defaultNodes.th.attributes, 12 | scope: { 13 | type: String, 14 | default: 'col', 15 | }, 16 | }, 17 | }, 18 | fence: { 19 | render: Fence, 20 | attributes: { 21 | language: { 22 | type: String, 23 | }, 24 | }, 25 | } 26 | } 27 | 28 | export default nodes 29 | -------------------------------------------------------------------------------- /operator/create-static-manifests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Generates the /manifests/deploy.yaml 3 | 4 | if [ -n "$DEBUG" ]; then 5 | set -x 6 | fi 7 | 8 | #set -o errexit 9 | set -o nounset 10 | set -o pipefail 11 | 12 | cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P 13 | 14 | MANIFEST="manifests/deploy.yaml" 15 | 16 | helm template netchecks-operator ./charts/netchecks \ 17 | --values examples/kind-installation/values.yaml \ 18 | --namespace netchecks \ 19 | > "${MANIFEST}" 20 | 21 | sed -i.bak '/app.kubernetes.io\/managed-by: Helm/d' "${MANIFEST}" 22 | sed -i.bak '/helm.sh/d' "${MANIFEST}" 23 | -------------------------------------------------------------------------------- /operator/tests/testdata/internal-config-map-check.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: internal-k8s-config-check 5 | annotations: 6 | description: Assert probe can access and validate configmap data 7 | spec: 8 | context: 9 | - name: somecontext 10 | configMap: 11 | name: some-config-map 12 | schedule: "*/10 * * * *" 13 | rules: 14 | - name: validate-configmap-value 15 | type: internal 16 | validate: 17 | message: Configmap should have particular value 18 | pattern: "somecontext.foo == 'bar'" 19 | -------------------------------------------------------------------------------- /operator/tests/testdata/should-fail.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: http-k8s-api-should-fail-ci 5 | namespace: default 6 | annotations: 7 | description: Assert pod can connect to k8s API 8 | spec: 9 | template: 10 | metadata: 11 | labels: 12 | optional-label: applied-to-test-pod 13 | rules: 14 | - name: kubernetes-version 15 | type: http 16 | url: https://kubernetes/devnull 17 | verify-tls-cert: false 18 | expected: pass 19 | validate: 20 | message: Http request to Kubernetes API should succeed. 21 | -------------------------------------------------------------------------------- /operator/tests/testdata/should-pass.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: http-k8s-api-should-pass-ci 5 | namespace: default 6 | annotations: 7 | description: Assert failure for pod can connect to k8s API 8 | spec: 9 | template: 10 | metadata: 11 | labels: 12 | optional-label: applied-to-test-pod 13 | rules: 14 | - name: kubernetes-version 15 | type: http 16 | url: https://kubernetes/version 17 | verify-tls-cert: false 18 | expected: pass 19 | validate: 20 | message: Http request to Kubernetes API should succeed. 21 | -------------------------------------------------------------------------------- /operator/examples/k8s-version.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: http-k8s-api-should-work 5 | namespace: default 6 | annotations: 7 | description: Assert pod can connect to k8s API 8 | spec: 9 | template: 10 | metadata: 11 | labels: 12 | optional-label: applied-to-test-pod 13 | schedule: "*/5 * * * *" 14 | rules: 15 | - name: kubernetes-version 16 | type: http 17 | url: https://kubernetes/version 18 | verify-tls-cert: false 19 | expected: pass 20 | validate: 21 | message: Http request to Kubernetes API should succeed. 22 | -------------------------------------------------------------------------------- /tests/testdata/config-with-multiple-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "cloudflare-dns-no-host", "rules": [{"type": "dns", "server": "1.1.1.1", "host": "foo.hardbyte.nz", "expected": "fail"}] }, 4 | { 5 | "name": "http-expected", 6 | "rules": [ 7 | {"type": "http", "url": "https://pie.dev/status/404", "expected": "fail"}, 8 | {"type": "http", "url": "https://pie.dev/status/401", "expected": "fail"}, 9 | {"type": "http", "url": "https://pie.dev/status/500", "expected": "fail"}, 10 | {"type": "http", "url": "https://pie.dev/delay/10", "expected": "fail"} 11 | ] 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /operator/tests/testdata/http-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: http-should-work 5 | annotations: 6 | description: Assert pod can connect to k8s API 7 | spec: 8 | template: 9 | metadata: 10 | labels: 11 | optional-label: applied-to-test-pod 12 | annotations: 13 | my: override-annotation 14 | rules: 15 | - name: kubernetes-version 16 | type: http 17 | url: https://kubernetes.default.svc/version 18 | verify-tls-cert: false 19 | expected: pass 20 | validate: 21 | message: Http request to Kubernetes API should succeed. 22 | 23 | -------------------------------------------------------------------------------- /operator/examples/cilium-cluster-wide-dns-restrictions/default-dns-netpol.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "cilium.io/v2" 2 | kind: CiliumNetworkPolicy 3 | metadata: 4 | name: intercept-dns 5 | spec: 6 | endpointSelector: {} 7 | egress: 8 | - toEndpoints: 9 | - matchLabels: 10 | "k8s:io.kubernetes.pod.namespace": kube-system 11 | "k8s:k8s-app": kube-dns 12 | toPorts: 13 | - ports: 14 | - port: "53" 15 | protocol: ANY 16 | rules: 17 | dns: 18 | # https://docs.cilium.io/en/v1.12/policy/language/#dns-based 19 | # Allow the DNS request for any URL: 20 | - matchPattern: "*" 21 | -------------------------------------------------------------------------------- /docs/src/styles/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Lexend'; 3 | font-style: normal; 4 | font-weight: 100 900; 5 | font-display: swap; 6 | src: url(/fonts/lexend.woff2) format('woff2'); 7 | } 8 | @font-face { 9 | font-family: 'Inter'; 10 | font-weight: 100 900; 11 | font-display: block; 12 | font-style: normal; 13 | font-named-instance: 'Regular'; 14 | src: url('/fonts/Inter-roman.var.woff2') format('woff2'); 15 | } 16 | @font-face { 17 | font-family: 'Inter'; 18 | font-weight: 100 900; 19 | font-display: block; 20 | font-style: italic; 21 | font-named-instance: 'Italic'; 22 | src: url('/fonts/Inter-italic.var.woff2') format('woff2'); 23 | } 24 | -------------------------------------------------------------------------------- /operator/examples/value-in-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: some-config-map 5 | data: 6 | foo: "bar" 7 | --- 8 | apiVersion: netchecks.io/v1 9 | kind: NetworkAssertion 10 | metadata: 11 | name: k8s-config-check 12 | annotations: 13 | description: Assert probe can access configmap data 14 | spec: 15 | context: 16 | - name: somecontext 17 | configMap: 18 | name: some-config-map 19 | schedule: "*/5 * * * *" 20 | rules: 21 | - name: validate-configmap-value 22 | type: internal 23 | validate: 24 | message: Configmap should have particular value 25 | pattern: "somecontext.foo == 'bar'" 26 | -------------------------------------------------------------------------------- /example-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | { 4 | "name": "example-assertion", 5 | "rules": [ 6 | { 7 | "type": "http", 8 | "url": "{{customdata.url}}", 9 | "headers": {"{{customdata.header}}": "{{ b64decode(token) }}"}, 10 | "validation": "parse_json(data.body).headers['X-Header'] == 'secret'" 11 | } 12 | ] 13 | } 14 | ], 15 | "contexts": [ 16 | {"name": "customdata", "type": "inline", "data": {"url": "https://pie.dev/headers", "header": "X-Header"}}, 17 | {"name": "token", "type": "inline", "data": "c2VjcmV0=="}, 18 | {"name": "selfref", "type": "file", "path": "example-config.json"} 19 | ] 20 | } -------------------------------------------------------------------------------- /operator/examples/validation/dns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: dns-should-work 5 | namespace: default 6 | annotations: 7 | description: Cluster should be able to lookup using public dns servers from default namespace. 8 | spec: 9 | # https://crontab.guru 10 | #schedule: "*/5 * * * *" 11 | #schedule: "@hourly" 12 | rules: 13 | - name: github-expected-ip 14 | type: dns 15 | server: 1.1.1.1 16 | host: github.com 17 | expected: pass 18 | validate: 19 | message: DNS requests to cloudflare's 1.1.1.1 should contain a specific IP address. 20 | pattern: "data['A'].contains('20.248.137.48')" 21 | -------------------------------------------------------------------------------- /docs/src/pages/docs/design-principles.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Design principles 3 | description: This is where we explain the design principles of Netchecks. 4 | --- 5 | 6 | Netchecks aims to be: 7 | 8 | - **Cloud native** - Netchecks is designed to run in Kubernetes and to be managed using Kubernetes resources. 9 | - **Extensible** - Netchecks is designed to be extended by adding new assertions and new assertion types. 10 | - **Simple** - Netchecks is designed to be simple to use. 11 | - **Secure** - Netchecks is designed to be secure by default. 12 | - **Reliable** - Netchecks is designed to be reliable and fault tolerant. 13 | - **Scalable** - Netchecks is designed to be scalable and to support large numbers of assertions. 14 | 15 | -------------------------------------------------------------------------------- /tests/testdata/simple-config-with-expected-failures.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "cloudflare-dns-no-host", "rules": [{"type": "dns", "server": "1.1.1.1", "host": "foo.hardbyte.nz", "expected": "fail"}] }, 4 | {"name": "http-expected-404", "rules": [{"type": "http", "url": "https://pie.dev/status/404", "expected": "fail"}]}, 5 | {"name": "http-expected-401", "rules": [{"type": "http", "url": "https://pie.dev/status/401", "expected": "fail"}]}, 6 | {"name": "http-expected-500", "rules": [{"type": "http", "url": "https://pie.dev/status/500", "expected": "fail"}]}, 7 | {"name": "http-expected-timeout", "rules": [{"type": "http", "url": "https://pie.dev/delay/1","timeout": 0.1, "expected": "fail"}]} 8 | ] 9 | } -------------------------------------------------------------------------------- /tests/testdata/simple-config-with-unexpected-failures.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "cloudflare-dns-no-host", "rules": [{"type": "dns", "server": "1.1.1.1", "host": "foo.hardbyte.nz", "expected": "pass"}] }, 4 | {"name": "http-expected-404", "rules": [{"type": "http", "url": "https://pie.dev/status/404", "expected": "pass"}]}, 5 | {"name": "http-expected-401", "rules": [{"type": "http", "url": "https://pie.dev/status/401", "expected": "pass"}]}, 6 | {"name": "http-expected-500", "rules": [{"type": "http", "url": "https://pie.dev/status/500", "expected": "pass"}]}, 7 | {"name": "http-expected-timeout", "rules": [{"type": "http", "url": "https://pie.dev/delay/1", "timeout": 0.1, "expected": "pass"}]} 8 | ] 9 | } -------------------------------------------------------------------------------- /.github/workflows/helm-chart.yaml: -------------------------------------------------------------------------------- 1 | name: Lint helm chart 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "charts/**" 9 | 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: "0" 22 | 23 | - name: chart-testing (ct lint) 24 | uses: helm/chart-testing-action@v2.0.1 25 | 26 | - name: Run Helm Chart lint 27 | run: | 28 | ct lint \ 29 | --target-branch=main \ 30 | --validate-maintainers=true \ 31 | --chart-dirs charts 32 | -------------------------------------------------------------------------------- /operator/examples/cilium-dns-restrictions/dns-netpol.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "cilium.io/v2" 2 | kind: CiliumNetworkPolicy 3 | metadata: 4 | name: intercept-dns 5 | spec: 6 | endpointSelector: {} 7 | egress: 8 | - toEndpoints: 9 | - matchLabels: 10 | "k8s:io.kubernetes.pod.namespace": kube-system 11 | "k8s:k8s-app": kube-dns 12 | toPorts: 13 | - ports: 14 | - port: "53" 15 | protocol: ANY 16 | rules: 17 | dns: 18 | # https://docs.cilium.io/en/stable/security/policy/language/#dns-based 19 | - matchPattern: "*" 20 | - toFQDNs: 21 | # allows L3 connections to github.com 22 | - matchName: "github.com" 23 | - matchPattern: "*.github.com" 24 | -------------------------------------------------------------------------------- /operator/tests/testdata/with-service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: netcheck-test-probe-account 5 | labels: 6 | app.kubernetes.io/instance: netchecks 7 | --- 8 | apiVersion: netchecks.io/v1 9 | kind: NetworkAssertion 10 | metadata: 11 | name: custom-service-account 12 | annotations: 13 | description: Assert probe can access secret data 14 | spec: 15 | template: 16 | spec: 17 | serviceAccountName: netcheck-test-probe-account 18 | context: 19 | - name: default 20 | inline: 21 | foo: "bar" 22 | rules: 23 | - name: check-value 24 | type: internal 25 | validate: 26 | message: Probe context should have particular value 27 | pattern: "default.foo == 'bar'" 28 | -------------------------------------------------------------------------------- /operator/examples/httpbin/http.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: http-should-work 5 | namespace: default 6 | annotations: 7 | description: Assert pod can connect to k8s API 8 | spec: 9 | schedule: "@hourly" 10 | rules: 11 | - name: kubernetes-version 12 | type: http 13 | url: https://kubernetes/version 14 | verify-tls-cert: false 15 | expected: pass 16 | validate: 17 | message: Http request to Kubernetes API should succeed. 18 | - name: httpbin-service 19 | type: http 20 | url: http://httpbin/version 21 | verify-tls-cert: false 22 | expected: pass 23 | validate: 24 | message: Http request to locally installed httpbin service should succeed. 25 | -------------------------------------------------------------------------------- /operator/examples/cilium-dns-restrictions/README.md: -------------------------------------------------------------------------------- 1 | 2 | This example deploys a cilium network policy that intercepts all DNS queries. 3 | 4 | Only cluster queries, and github.com should be allowed. 5 | 6 | The NetworkAssertion checks that is the case. 7 | 8 | 9 | Running the full example locally is something along these lines: 10 | 11 | ```shell 12 | 13 | kind create cluster 14 | kubectl config use-context kind-kind 15 | 16 | cilium install --helm-set cni.chainingMode=portmap 17 | cilium hubble enable --ui 18 | 19 | # Install the cilium network policy 20 | kubectl apply -f ./dns-netpol.yaml 21 | 22 | # Install the network assertion 23 | kubectl apply -f ./dns-assertion.yaml 24 | 25 | # Can see what gets blocked using hubble 26 | cilium hubble port-forward& 27 | 28 | 29 | ``` 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/testdata/simple-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "assertions": [ 3 | {"name": "default-dns", "rules": [{"type": "dns", "host": "github.com"}] }, 4 | {"name": "cloudflare-dns", "rules": [{"type": "dns", "server": "1.1.1.1", "host": "github.com"}] }, 5 | {"name": "github-status", "rules": [{"type": "http", "url": "https://github.com/status"}] }, 6 | {"name": "skip-tls-verification", "rules": [{"type": "http", "url": "https://self-signed.badssl.com/", "verify-tls-cert": false}]}, 7 | {"name": "fail-for-invalid-tls", "rules": [{"type": "http", "url": "https://self-signed.badssl.com/", "expected": "fail"}]}, 8 | {"name": "assert-ssl-error-on-invalid-tls", "rules": [ 9 | {"type": "http", "url": "https://self-signed.badssl.com/", "validation": "data['exception-type'] == 'SSLError'"}]} 10 | ] 11 | } -------------------------------------------------------------------------------- /operator/charts/netchecks/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "netchecks.fullname" . }} 5 | labels: {{ include "netchecks.labels" . | nindent 4 }} 6 | data: 7 | config.json: |- 8 | { 9 | "probe": { 10 | "image": { 11 | "pullPolicy":{{ .Values.probeConfig.image.pullPolicy | quote }}, 12 | "repository":{{ .Values.probeConfig.image.repository | quote }}, 13 | "tag":{{ .Values.probeConfig.image.tag | default .Chart.AppVersion | quote }} 14 | }, 15 | "imagePullSecrets": {{ .Values.probeConfig.imagePullSecrets | toJson }}, 16 | "podAnnotations": {{ .Values.probeConfig.podAnnotations | toJson }}, 17 | "resources": {{ .Values.probeConfig.resources | toJson }} 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/helm-chart-release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Helm Charts 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | permissions: 12 | contents: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - name: Configure Git 20 | run: | 21 | git config user.name "$GITHUB_ACTOR" 22 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 23 | - name: Install Helm 24 | uses: azure/setup-helm@v3 25 | - name: Run chart-releaser 26 | uses: hardbyte/chart-releaser-action@main 27 | with: 28 | charts_dir: operator/charts 29 | mark_as_latest: false 30 | env: 31 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 32 | -------------------------------------------------------------------------------- /operator/tests/testdata/with-configmap-data.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: some-config-map 5 | data: 6 | API_TOKEN: "some-data-from-a-configmap!" 7 | --- 8 | apiVersion: netchecks.io/v1 9 | kind: NetworkAssertion 10 | metadata: 11 | name: http-with-external-data 12 | annotations: 13 | description: Assert probe can access configmap data 14 | spec: 15 | context: 16 | - name: somecontext 17 | configMap: 18 | name: some-config-map 19 | rules: 20 | - name: pie-dev-headers-and-validation 21 | type: http 22 | url: https://pie.dev/headers 23 | headers: 24 | "X-Netcheck-Header": "{{ somecontext.API_TOKEN }}" 25 | expected: pass 26 | validate: 27 | message: Http request with header to pie.dev service should reply with header value 28 | pattern: "parse_json(data.body).headers['X-Netcheck-Header'] == somecontext.API_TOKEN" 29 | -------------------------------------------------------------------------------- /docs/src/pages/docs/releasing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Release 3 | description: How to release a new version of Netchecks 4 | --- 5 | 6 | ## Releasing Netchecks 7 | 8 | Netchecks is released via GitHub Actions. To make a release 9 | 10 | - Update `version` in both `pyproject.toml` and `operator/pyproject.toml`. Ensure both versions match. 11 | - Update `version` in `operator/charts/netchecks/Chart.yaml` 12 | - Update `appVersion` in `operator/charts/netchecks/Chart.yaml`. Ensure this matches the `version` in the aforementioned `pyproject.toml` files. 13 | - Update `version` in `operator/charts/netchecks/Chart.yaml`. 14 | - Make PR against the `main` branch and merge. 15 | - Create a GitHub release with the tag `v`, where `semver` is the same version as set in the `pyproject.toml` files. 16 | - Note that a Helm release will automatically be created. 17 | 18 | Note that the version needs to follow [semantic versioning](https://semver.org/). 19 | -------------------------------------------------------------------------------- /operator/examples/default-k8s/http.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: test-http-netcheck-sa 5 | namespace: default 6 | --- 7 | apiVersion: netchecks.io/v1 8 | kind: NetworkAssertion 9 | metadata: 10 | name: http-should-work 11 | namespace: default 12 | annotations: 13 | description: Assert pod can connect to k8s API 14 | spec: 15 | template: 16 | spec: 17 | serviceAccountName: test-http-netcheck-sa 18 | securityContext: 19 | runAsUser: 1000 20 | metadata: 21 | labels: 22 | optional-label: applied-to-test-pod 23 | annotations: 24 | my: override-annotation 25 | schedule: "@hourly" 26 | rules: 27 | - name: kubernetes-version 28 | type: http 29 | url: https://kubernetes/version 30 | verify-tls-cert: false 31 | expected: pass 32 | validate: 33 | message: Http request to Kubernetes API should succeed. 34 | 35 | -------------------------------------------------------------------------------- /operator/examples/cilium-cluster-wide-dns-restrictions/README.md: -------------------------------------------------------------------------------- 1 | 2 | This example deploys a cilium network policy that intercepts and filters all DNS queries in the default namespace. 3 | Any DNS queries are allowed to be made via `kube-dns` in the `kube-system` namespace. 4 | 5 | A further `CiliumNetworkPolicy` applied to the `kube-system` namespace restricts the external lookups that `kube-dns` 6 | can carry out. 7 | 8 | 9 | Apply the network policies: 10 | ``` 11 | kubectl apply -n default -f examples/cilium-cluster-wide-dns-restrictions/default-dns-netpol.yaml 12 | kubectl apply -n kube-system -f examples/cilium-cluster-wide-dns-restrictions/kube-dns-netpol.yaml 13 | ``` 14 | 15 | Apply the network assertions: 16 | 17 | ``` 18 | kubectl apply -n default -f examples/cilium-cluster-wide-dns-restrictions/cluster-dns-should-work.yaml 19 | kubectl apply -n default -f examples/cilium-cluster-wide-dns-restrictions/dns-res-restrictions-assertion.yaml 20 | ``` -------------------------------------------------------------------------------- /operator/examples/validation/http-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: some-config-map 5 | data: 6 | API_TOKEN: some-data-from-a-configmap 7 | --- 8 | apiVersion: netchecks.io/v1 9 | kind: NetworkAssertion 10 | metadata: 11 | name: http-with-external-data 12 | namespace: default 13 | annotations: 14 | description: Assert probe can access configmap data 15 | spec: 16 | context: 17 | - name: somecontext 18 | configMap: 19 | name: some-config-map 20 | rules: 21 | - name: pie-dev-headers-and-validation 22 | type: http 23 | url: https://pie.dev/headers 24 | headers: 25 | "X-Netcheck-Header": "{{ somecontext.API_TOKEN }}" 26 | expected: pass 27 | validate: 28 | message: Http request with header to pie.dev service should reply with header value 29 | pattern: "parse_json(data.body).headers['X-Netcheck-Header'] == 'some-data-from-a-configmap'" 30 | -------------------------------------------------------------------------------- /docs/src/styles/prism.css: -------------------------------------------------------------------------------- 1 | pre[class*='language-'] { 2 | color: theme('colors.slate.50'); 3 | } 4 | 5 | .token.tag, 6 | .token.class-name, 7 | .token.selector, 8 | .token.selector .class, 9 | .token.selector.class, 10 | .token.function { 11 | color: theme('colors.pink.400'); 12 | } 13 | 14 | .token.attr-name, 15 | .token.keyword, 16 | .token.rule, 17 | .token.pseudo-class, 18 | .token.important { 19 | color: theme('colors.slate.300'); 20 | } 21 | 22 | .token.module { 23 | color: theme('colors.pink.400'); 24 | } 25 | 26 | .token.attr-value, 27 | .token.class, 28 | .token.string, 29 | .token.property { 30 | color: theme('colors.sky.300'); 31 | } 32 | 33 | .token.punctuation, 34 | .token.attr-equals { 35 | color: theme('colors.slate.500'); 36 | } 37 | 38 | .token.unit, 39 | .language-css .token.function { 40 | color: theme('colors.teal.200'); 41 | } 42 | 43 | .token.comment, 44 | .token.operator, 45 | .token.combinator { 46 | color: theme('colors.slate.400'); 47 | } 48 | -------------------------------------------------------------------------------- /operator/tests/testdata/with-secret-data.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: some-secret 5 | data: 6 | API_TOKEN: a3E0Z2lodnN6emduMXAwcg== 7 | UNWANTED: aGlkZGVu 8 | --- 9 | apiVersion: netchecks.io/v1 10 | kind: NetworkAssertion 11 | metadata: 12 | name: http-with-external-secret-data 13 | annotations: 14 | description: Assert probe can access secret data 15 | spec: 16 | context: 17 | - name: somecontext 18 | secret: 19 | name: some-secret 20 | items: 21 | - key: API_TOKEN 22 | path: API_TOKEN 23 | rules: 24 | - name: pie-dev-headers-and-validation 25 | type: http 26 | url: https://pie.dev/headers 27 | headers: 28 | "X-Netcheck-Header": "{{ somecontext.API_TOKEN }}" 29 | expected: pass 30 | validate: 31 | message: Http request with header to pie.dev service should reply with header value 32 | pattern: "parse_json(data.body).headers['X-Netcheck-Header'] == somecontext.API_TOKEN" 33 | -------------------------------------------------------------------------------- /operator/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "netcheck-operator" 3 | version = "0.5.6" 4 | description = "Netcheck is a cloud native tool for specifying and regularly checking assertions about network conditions. Organisations use netcheck to proactively verify whether security controls are working as intended, alerting them to misconfiguration and potential threats." 5 | authors = ["Brian Thorne "] 6 | readme = "README.md" 7 | packages = [{include = "netchecks_operator"}] 8 | 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.11" 12 | kopf = "^1.37.1" 13 | kubernetes = "^29.0" 14 | rich = "^13.3" 15 | structlog = "^23.1.0" 16 | prometheus-client = "^0.16.0" 17 | pydantic = "^1.10.7" 18 | opentelemetry-sdk = "^1.24.0" 19 | opentelemetry-exporter-otlp = "^1.24.0" 20 | opentelemetry-exporter-prometheus = "^0.46b0" 21 | 22 | 23 | [tool.poetry.group.dev.dependencies] 24 | pytest = "^8.1" 25 | ruff = "^0.3" 26 | 27 | [build-system] 28 | requires = ["poetry-core"] 29 | build-backend = "poetry.core.masonry.api" 30 | -------------------------------------------------------------------------------- /operator/examples/default-k8s/dns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: dns-should-work 5 | namespace: default 6 | annotations: 7 | description: Cluster should be able to lookup using public dns servers from default namespace. 8 | spec: 9 | # https://crontab.guru 10 | schedule: "*/10 * * * *" 11 | #schedule: "@hourly" 12 | rules: 13 | - name: cloudflare-dns-lookup 14 | type: dns 15 | server: 1.1.1.1 16 | host: github.com 17 | expected: fail 18 | validate: 19 | message: DNS requests to cloudflare's 1.1.1.1 should be blocked. 20 | - name: google-dns-lookup 21 | type: dns 22 | server: 8.8.8.8 23 | host: github.com 24 | expected: fail 25 | validate: 26 | message: DNS requests to Google's 8.8.8.8 should fail. 27 | - name: default-dns-lookup 28 | type: dns 29 | host: github.com 30 | expected: pass 31 | validate: 32 | message: DNS requests using default namesever should succeed. 33 | -------------------------------------------------------------------------------- /operator/charts/netchecks/README.md: -------------------------------------------------------------------------------- 1 | # Netchecks 2 | 3 | Proactively verifies whether your security controls are working as intended with a policy as code approach, making no assumptions about how your security controls are implemented. Learn more at [netchecks.io](https://netchecks.io). 4 | 5 | Netchecks is written and maintained by Brian Thorne [@hardbyte](https://github.com/hardbyte). 6 | 7 | 8 | ## Documentation 9 | 10 | The full documentation can be found at [docs.netchecks.io](https://docs.netchecks.io/) and the [GitHub repository](https://github.com/hardbyte/netchecks/tree/main/operator). 11 | 12 | ## Prerequisites 13 | 14 | * Kubernetes 1.21+ 15 | 16 | 17 | ## Installing the Chart 18 | 19 | Full installation instructions can be found in the [documentation installation page](https://docs.netchecks.io/docs/installation). 20 | 21 | To install the chart 22 | 23 | ```bash 24 | helm repo add netchecks https://hardbyte.github.io/netchecks 25 | helm upgrade --install netchecks netchecks/netchecks -n netchecks --create-namespace 26 | 27 | ``` 28 | 29 | ## Source Code 30 | 31 | -------------------------------------------------------------------------------- /operator/charts/netchecks/crds/networkassertions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: networkassertions.netchecks.io 5 | labels: 6 | app.kubernetes.io/part-of: netcheck 7 | spec: 8 | scope: Namespaced 9 | group: netchecks.io 10 | names: 11 | kind: NetworkAssertion 12 | listKind: NetworkAssertionList 13 | plural: networkassertions 14 | singular: networkassertion 15 | shortNames: 16 | - nas 17 | versions: 18 | - name: v1 19 | served: true 20 | storage: true 21 | additionalPrinterColumns: 22 | - jsonPath: .spec.schedule 23 | name: Schedule 24 | type: string 25 | schema: 26 | openAPIV3Schema: 27 | type: object 28 | description: NetworkAssertion is the Schema for the networkassertions API 29 | properties: 30 | spec: 31 | type: object 32 | x-kubernetes-preserve-unknown-fields: true 33 | status: 34 | type: object 35 | x-kubernetes-preserve-unknown-fields: true 36 | -------------------------------------------------------------------------------- /operator/tests/testdata/with-context-data.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: a-config-map 5 | data: 6 | yaml_data: | 7 | toplevel: 8 | nestedkey: "yaml-data" 9 | array: 10 | - "value" 11 | - "value2" 12 | --- 13 | apiVersion: netchecks.io/v1 14 | kind: NetworkAssertion 15 | metadata: 16 | name: http-with-inline-data 17 | annotations: 18 | description: Assert probe can access configmap data 19 | spec: 20 | context: 21 | - name: originalcontext 22 | configMap: 23 | name: a-config-map 24 | - name: derivedcontext 25 | inline: 26 | key: "{{ parse_yaml(originalcontext.yaml_data).toplevel.nestedkey }}" 27 | rules: 28 | - name: pie-dev-headers-and-validation 29 | type: http 30 | url: https://pie.dev/headers 31 | headers: 32 | "X-Netcheck-Header": "{{ derivedcontext.key }}" 33 | expected: pass 34 | validate: 35 | message: Http request with header to pie.dev service should reply with header value 36 | pattern: "parse_json(data.body).headers['X-Netcheck-Header'] == derivedcontext.key" 37 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindui-syntax", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "browserslist": "defaults, not ie <= 11", 12 | "dependencies": { 13 | "@docsearch/react": "^3.3.4", 14 | "@headlessui/react": "^1.7.2", 15 | "@markdoc/markdoc": "^0.2.2", 16 | "@markdoc/next.js": "^0.2.2", 17 | "@sindresorhus/slugify": "^2.1.0", 18 | "@tailwindcss/typography": "^0.5.7", 19 | "autoprefixer": "^10.4.12", 20 | "clsx": "^1.2.1", 21 | "focus-visible": "^5.2.0", 22 | "next": "14.2.32", 23 | "postcss-focus-visible": "^6.0.4", 24 | "postcss-import": "^14.1.0", 25 | "prism-react-renderer": "^1.3.5", 26 | "react": "18.2.0", 27 | "react-dom": "18.2.0", 28 | "react-icons": "^4.8.0", 29 | "tailwindcss": "^3.2.1" 30 | }, 31 | "devDependencies": { 32 | "eslint": "8.26.0", 33 | "eslint-config-next": "13.0.2", 34 | "prettier": "^2.7.1", 35 | "prettier-plugin-tailwindcss": "^0.1.13" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link' 3 | import clsx from 'clsx' 4 | 5 | const styles = { 6 | primary: 7 | 'rounded-full bg-sky-300 py-2 px-4 text-sm font-semibold text-slate-900 hover:bg-sky-200 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-300/50 active:bg-sky-500', 8 | secondary: 9 | 'rounded-full bg-slate-800 py-2 px-4 text-sm font-medium text-white hover:bg-slate-700 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400', 10 | } 11 | 12 | export function Button({ variant = 'primary', className, href, icon, ...props }) { 13 | className = clsx(styles[variant], className) 14 | 15 | const content = ( 16 | <> 17 | {icon && {icon}} 18 | {props.children} 19 | 20 | ); 21 | 22 | return href ? ( 23 | 24 | {content} 25 | 26 | ) : ( 27 | ); 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: [push] 4 | 5 | jobs: 6 | check-linters: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: reviewdog/action-shellcheck@v1 11 | with: 12 | filter_mode: file 13 | - uses: hadolint/hadolint-action@v3.1.0 14 | with: 15 | dockerfile: Dockerfile 16 | recursive: true 17 | no-fail: 'true' 18 | - uses: chartboost/ruff-action@v1 19 | 20 | check-versions: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install uv 25 | id: setup-uv 26 | uses: astral-sh/setup-uv@v2 27 | with: 28 | enable-cache: true 29 | - run: | 30 | uv run --with pyyaml ./scripts/check_versions.py 31 | 32 | typocheck: 33 | name: Spellcheck with Typos 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout Actions Repository 37 | uses: actions/checkout@v4 38 | - name: Check for typos 39 | uses: crate-ci/typos@master 40 | with: 41 | config: ./.typos.toml 42 | -------------------------------------------------------------------------------- /operator/examples/validation/http.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: http-should-work 5 | namespace: default 6 | annotations: 7 | description: Assert pod can connect to k8s API 8 | spec: 9 | #schedule: "@hourly" 10 | rules: 11 | - name: pie-dev-200 12 | type: http 13 | url: https://pie.dev/status/200 14 | expected: pass 15 | validate: 16 | message: Http request to pie.dev service should succeed. 17 | - name: pie-dev-headers 18 | type: http 19 | url: https://pie.dev/headers 20 | headers: 21 | "X-Netcheck-Header": "testvalue" 22 | expected: pass 23 | validate: 24 | message: Http request with header to pie.dev service should succeed. 25 | - name: pie-dev-headers-and-validation 26 | type: http 27 | url: https://pie.dev/headers 28 | headers: 29 | "X-Netcheck-Header": "secret" 30 | expected: pass 31 | validate: 32 | message: Http request with header to pie.dev service should reply with header value 33 | pattern: "parse_json(data.body).headers['X-Netcheck-Header'] == 'secret'" 34 | -------------------------------------------------------------------------------- /docs/src/components/Logo.jsx: -------------------------------------------------------------------------------- 1 | function LogomarkPaths() { 2 | return ( 3 | 4 | 5 | 10 | 11 | ) 12 | } 13 | 14 | export function Logomark(props) { 15 | return ( 16 | 19 | ) 20 | } 21 | 22 | export function Logo(props) { 23 | return ( 24 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /operator/tests/testdata/with-verbose-context.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: b-config-map 5 | data: 6 | yaml_data: | 7 | toplevel: 8 | nestedkey: "yaml-data" 9 | array: 10 | - "value" 11 | - "value2" 12 | --- 13 | apiVersion: netchecks.io/v1 14 | kind: NetworkAssertion 15 | metadata: 16 | name: http-with-verbose-context 17 | annotations: 18 | description: Assert probe outputs configmap data when spec.verbose=true 19 | spec: 20 | disableRedaction: true 21 | context: 22 | - name: originalcontext 23 | configMap: 24 | name: b-config-map 25 | - name: derivedcontext 26 | inline: 27 | key: "{{ parse_yaml(originalcontext.yaml_data).toplevel.nestedkey }}" 28 | rules: 29 | - name: pie-dev-headers-and-validation 30 | type: http 31 | url: https://pie.dev/headers 32 | headers: 33 | "X-Netcheck-Header": "{{ derivedcontext.key }}" 34 | expected: pass 35 | validate: 36 | message: Http request with header to pie.dev service should reply with header value 37 | pattern: "parse_json(data.body).headers['X-Netcheck-Header'] == derivedcontext.key" 38 | -------------------------------------------------------------------------------- /operator/examples/httpbin/httpbin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: httpbin 5 | --- 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | name: httpbin 10 | labels: 11 | app: httpbin 12 | service: httpbin 13 | spec: 14 | ports: 15 | - name: http 16 | port: 8000 17 | targetPort: 8080 18 | selector: 19 | app: httpbin 20 | --- 21 | apiVersion: apps/v1 22 | kind: Deployment 23 | metadata: 24 | name: httpbin 25 | labels: 26 | app: httpbin 27 | spec: 28 | replicas: 1 29 | selector: 30 | matchLabels: 31 | app: httpbin 32 | version: v1 33 | template: 34 | metadata: 35 | labels: 36 | app: httpbin 37 | version: v1 38 | spec: 39 | serviceAccountName: httpbin 40 | securityContext: 41 | runAsUser: 1000 42 | containers: 43 | - image: mccutchen/go-httpbin:v2.4.1 44 | imagePullPolicy: IfNotPresent 45 | name: httpbin 46 | ports: 47 | - containerPort: 8080 48 | resources: 49 | requests: 50 | cpu: 50m 51 | memory: 128Mi 52 | limits: 53 | cpu: 500m 54 | memory: 256Mi 55 | 56 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "netcheck" 3 | version = "0.5.6" 4 | description = "Netchecks is a cloud native tool for specifying and regularly checking assertions about network conditions. Use netchecks to proactively verify whether security controls are working as intended, alerting on misconfiguration." 5 | readme = "README.md" 6 | authors = [ 7 | {name = "Brian Thorne", email = "brian@hardbyte.nz"}, 8 | ] 9 | requires-python = "<4.0,>=3.10" 10 | dependencies = [ 11 | "dnspython<3.0,>=2.2", 12 | "requests<3.0,>=2.28", 13 | "typer<1.0,>=0.9", 14 | "pydantic<3.0,>=1.10", 15 | "rich<14.0.0,>=10.11.0", 16 | "cel-python<1.0.0,>=0.1.5", 17 | ] 18 | 19 | 20 | [tool.ruff] 21 | line-length = 120 22 | 23 | [dependency-groups] 24 | dev = [ 25 | "pytest<9.0,>=7.2", 26 | "coveralls<4.0.0,>=3.3.1", 27 | "pytest-cov<5.0.0,>=4.0.0", 28 | "ruff<0.3.4,>=0.0.241", 29 | ] 30 | 31 | [project.scripts] 32 | netcheck = "netcheck.cli:app" 33 | 34 | [tool.setuptools.packages.find] 35 | where = ["."] 36 | include = ["netcheck", "netcheck.*"] 37 | exclude = ["operator", "scripts", "tests", "docs"] 38 | 39 | 40 | [build-system] 41 | requires = ["setuptools", "wheel"] 42 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: ['./src/**/*.{js,jsx}'], 6 | darkMode: 'class', 7 | theme: { 8 | fontSize: { 9 | xs: ['0.75rem', { lineHeight: '1rem' }], 10 | sm: ['0.875rem', { lineHeight: '1.5rem' }], 11 | base: ['1rem', { lineHeight: '2rem' }], 12 | lg: ['1.125rem', { lineHeight: '1.75rem' }], 13 | xl: ['1.25rem', { lineHeight: '2rem' }], 14 | '2xl': ['1.5rem', { lineHeight: '2.5rem' }], 15 | '3xl': ['2rem', { lineHeight: '2.5rem' }], 16 | '4xl': ['2.5rem', { lineHeight: '3rem' }], 17 | '5xl': ['3rem', { lineHeight: '3.5rem' }], 18 | '6xl': ['3.75rem', { lineHeight: '1' }], 19 | '7xl': ['4.5rem', { lineHeight: '1' }], 20 | '8xl': ['6rem', { lineHeight: '1' }], 21 | '9xl': ['8rem', { lineHeight: '1' }], 22 | }, 23 | extend: { 24 | fontFamily: { 25 | sans: ['Inter', ...defaultTheme.fontFamily.sans], 26 | display: ['Lexend', ...defaultTheme.fontFamily.sans], 27 | }, 28 | maxWidth: { 29 | '8xl': '88rem', 30 | }, 31 | }, 32 | }, 33 | plugins: [require('@tailwindcss/typography')], 34 | } 35 | -------------------------------------------------------------------------------- /docs/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/markdoc/tags.js: -------------------------------------------------------------------------------- 1 | import { Callout } from '@/components/Callout' 2 | import { QuickLink, QuickLinks } from '@/components/QuickLinks' 3 | 4 | const tags = { 5 | callout: { 6 | attributes: { 7 | title: { type: String }, 8 | type: { 9 | type: String, 10 | default: 'note', 11 | matches: ['note', 'warning', 'beginner'], 12 | errorLevel: 'critical', 13 | }, 14 | }, 15 | render: Callout, 16 | }, 17 | figure: { 18 | selfClosing: true, 19 | attributes: { 20 | src: { type: String }, 21 | alt: { type: String }, 22 | caption: { type: String }, 23 | }, 24 | render: ({ src, alt = '', caption }) => ( 25 |
26 | {/* eslint-disable-next-line @next/next/no-img-element */} 27 | {alt} 28 |
{caption}
29 |
30 | ), 31 | }, 32 | 'quick-links': { 33 | render: QuickLinks, 34 | }, 35 | 'quick-link': { 36 | selfClosing: true, 37 | render: QuickLink, 38 | attributes: { 39 | title: { type: String }, 40 | description: { type: String }, 41 | icon: { type: String }, 42 | href: { type: String }, 43 | }, 44 | } 45 | 46 | } 47 | 48 | export default tags 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS build 2 | 3 | ENV UV_LINK_MODE=copy \ 4 | UV_COMPILE_BYTECODE=1 5 | 6 | # Sync dependencies using UV, but don't install the project yet 7 | # Mount pyproject.toml and uv.lock to install dependencies 8 | WORKDIR /app 9 | RUN --mount=type=cache,target=/root/.cache \ 10 | --mount=type=bind,source=uv.lock,target=uv.lock \ 11 | --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ 12 | uv sync --frozen --no-dev --no-install-project 13 | 14 | # Copy the application code to the build stage 15 | ADD . /app 16 | 17 | RUN --mount=type=cache,target=/root/.cache \ 18 | uv sync --frozen --no-dev 19 | 20 | # Runtime Stage 21 | FROM python:3.12-slim-bookworm 22 | LABEL org.opencontainers.image.source=https://github.com/hardbyte/netchecks 23 | 24 | # Set environment variables for Python 25 | ENV PYTHONDONTWRITEBYTECODE=1 \ 26 | PYTHONUNBUFFERED=1 \ 27 | # VIRTUAL_ENV=/app/.venv \ 28 | PATH="/app/.venv/bin:$PATH" \ 29 | USERNAME=netchecks \ 30 | USER_UID=1000 \ 31 | USER_GID=1000 32 | 33 | 34 | # Copy the virtual environment and application from the build stage 35 | COPY --from=build --chown=${USERNAME}:${USER_GID} /app /app 36 | 37 | ENTRYPOINT ["netcheck"] 38 | CMD ["http", "-v"] 39 | -------------------------------------------------------------------------------- /docs/src/pages/docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: Install Netchecks in a Kubernetes Cluster 4 | --- 5 | 6 | Netchecks operator is installed via Helm or directly via Kubernetes manifests. 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | Netchecks should work on any Kubernetes cluster version `1.21` or later. Helm 3 is recommended for installation, although static manifests are also provided. 13 | 14 | 15 | ## Installation 16 | 17 | 18 | ### Helm 19 | 20 | The helm chart is available on [Artifact Hub](https://artifacthub.io/packages/helm/netchecks/netchecks/). To install the operator 21 | 22 | ```shell 23 | helm repo add netchecks https://hardbyte.github.io/netchecks 24 | helm upgrade --install netchecks netchecks/netchecks -n netchecks --create-namespace 25 | ``` 26 | 27 | 28 | ### Static Manifests 29 | 30 | Alternatively, install the NetworkAssertion CRDs and the Netchecks operator with: 31 | 32 | ```shell 33 | kubectl apply -f https://github.com/hardbyte/netchecks/raw/main/operator/manifests/deploy.yaml 34 | ``` 35 | 36 | 37 | ## Software Supply Chain Integrity 38 | 39 | Netchecks docker images are all built by GitHub actions, cryptographically signed using [SigStore cosign](https://github.com/sigstore/cosign) 40 | and hosted on GitHub Container Registry (GHCR). -------------------------------------------------------------------------------- /docs/src/components/icons/InstallationIcon.jsx: -------------------------------------------------------------------------------- 1 | import { DarkMode, Gradient, LightMode } from '@/components/Icon' 2 | 3 | export function InstallationIcon({ id, color }) { 4 | return ( 5 | <> 6 | 7 | 12 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /operator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | LABEL org.opencontainers.image.source=https://github.com/hardbyte/netchecks 3 | 4 | # Configure Poetry 5 | ENV USERNAME=netchecks \ 6 | USER_UID=1000 \ 7 | USER_GID=1000 \ 8 | POETRY_VERSION=1.7.1 \ 9 | POETRY_HOME=/home/netchecks/bin/poetry \ 10 | POETRY_VENV=/home/netchecks/bin/poetry-venv \ 11 | POETRY_CACHE_DIR=/home/netchecks/bin/.cache \ 12 | PYTHONDONTWRITEBYTECODE=1 \ 13 | PYTHONUNBUFFERED=1 14 | 15 | RUN groupadd --gid ${USER_GID} ${USERNAME} \ 16 | && useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME} 17 | 18 | USER ${USERNAME} 19 | 20 | # Install poetry separated from system interpreter 21 | RUN python3 -m venv ${POETRY_VENV} \ 22 | && ${POETRY_VENV}/bin/pip install -U pip setuptools --no-cache-dir \ 23 | && ${POETRY_VENV}/bin/pip install poetry==${POETRY_VERSION} --no-cache-dir 24 | 25 | # Add `poetry` to PATH 26 | ENV PATH="${PATH}:${POETRY_VENV}/bin" 27 | 28 | WORKDIR /app 29 | 30 | # Install dependencies 31 | COPY --chown=${USERNAME}:${USER_GID} poetry.lock* pyproject.toml README.md ./ 32 | RUN poetry install --no-root --no-cache 33 | 34 | COPY --chown=${USERNAME}:${USER_GID} ./netchecks_operator/ /app/netchecks_operator/ 35 | RUN poetry install --no-cache 36 | CMD ["poetry", "run", "kopf", "run", "/app/netchecks_operator/main.py", "--all-namespaces", "--liveness=http://0.0.0.0:8080/healthz"] 37 | -------------------------------------------------------------------------------- /operator/tests/testdata/cluster-dns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: cluster-dns-should-work 5 | annotations: 6 | description: Check cluster dns behaviour 7 | spec: 8 | rules: 9 | - name: external-dns-host-lookup-should-work 10 | type: dns 11 | host: github.com 12 | expected: pass 13 | validate: 14 | message: DNS lookup of an external host using default nameserver. 15 | - name: approved-dns-host-subdomain-lookup-should-work 16 | type: dns 17 | host: status.github.com 18 | expected: pass 19 | validate: 20 | message: DNS requests for a subdomain of an external host. 21 | - name: k8s-svc-dns-lookup-should-work 22 | type: dns 23 | host: kubernetes.default.svc 24 | expected: pass 25 | validate: 26 | message: DNS lookup of the kubernetes service should work. 27 | - name: k8s-svc-with-cluster-domain-lookup-should-work 28 | type: dns 29 | host: kubernetes.default.svc.cluster.local 30 | expected: pass 31 | validate: 32 | message: DNS lookup of the fqdn kubernetes service should work. 33 | - name: missing-svc-dns-lookup-should-fail 34 | type: dns 35 | host: unlikely-a-real-service.default.svc.cluster.local 36 | expected: fail 37 | validate: 38 | message: DNS lookup of the missing service should fail. -------------------------------------------------------------------------------- /docs/src/components/QuickLinks.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | import { Icon } from '@/components/Icon' 4 | 5 | export function QuickLinks({ children }) { 6 | return ( 7 |
8 | {children} 9 |
10 | ) 11 | } 12 | 13 | export function QuickLink({ title, description, href, icon }) { 14 | return ( 15 |
16 |
17 |
18 | 19 |

20 | 21 | 22 | {title} 23 | 24 |

25 |

26 | {description} 27 |

28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /docs/src/pages/docs/how-to-contribute.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to contribute 3 | description: How can you contribute to Netchecks? 4 | --- 5 | 6 | The Netchecks project is open source and welcomes contributions from the community. There are many ways to contribute to 7 | the project, including: 8 | 9 | - Reporting bugs 10 | - Suggesting new features 11 | - Contributing code 12 | - Contributing documentation 13 | 14 | The place to start is GitHub. If you find a bug or have a feature request, please open an issue in the appropriate 15 | repository. If you want to contribute code, please open a pull request. 16 | 17 | ## Code Repository 18 | 19 | The code is available under the [netchecks](https://github.com/hardbyte/netchecks/) Github repository. 20 | 21 | ### Docs 22 | 23 | Documentation is available in the folder [netchecks/docs](https://github.com/hardbyte/netchecks/tree/main/docs). 24 | 25 | 26 | ### Netcheck Probe 27 | 28 | The command line tool that runs the network probes is called `netcheck`. It is implemented in Python and is available 29 | as a PyPi package. The source code is available in the [netchecks](https://github.com/hardbyte/netchecks/) repository 30 | on GitHub. 31 | 32 | ### Netcheck Operator 33 | 34 | The netchecks operator is implemented in Python using the [kopf](https://kopf.readthedocs.io/en/stable) framework 35 | and is available in the [operator](https://github.com/hardbyte/netchecks/tree/main/operator) folder on GitHub. 36 | -------------------------------------------------------------------------------- /docs/src/pages/docs/architecture-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Architecture guide 3 | description: Netchecks architecture overview. 4 | --- 5 | 6 | Netchecks runs in Kubernetes as an [operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/). The 7 | operator is implemented in Python using the [kopf](https://kopf.readthedocs.io/en/stable) framework. 8 | 9 | The netchecks operator: 10 | - Listens for `NetworkAssertion` resources across the kubernetes cluster and creates `CronJobs` (or `Jobs`) for each of them. 11 | - Probe pods are created by the `CronJob` and run the tests that make up a particular network assertion. External data may be mounted into the Pod for use by the probe. 12 | - Listens for _probe_ Pods created by the NetworkAssertion's CronJob and parses assertion results from the Pod logs. 13 | - Creates and updates `PolicyReport` resources for each NetworkAssertion in response to the assertion results. 14 | 15 | Each probe pod uses the `netchecks` docker image to run the tests that make up a particular network assertion. 16 | 17 | 18 | ![](/images/architecture/Netcheck-High-Level-Lifecycle.png) 19 | 20 | --- 21 | 22 | The `netchecks` image is based on the [python:3.11-slim](https://hub.docker.com/_/python) image. 23 | 24 | [Kyverno's PolicyReporter](https://kyverno.github.io/policy-reporter/) is optionally installed alongside Netchecks to 25 | provide a convenient way to expose metrics, view the results, and generate notifications. 26 | -------------------------------------------------------------------------------- /operator/examples/cilium-dns-restrictions/dns-restrictions-assertion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: dns-restrictions-should-work 5 | annotations: 6 | description: Check cluster dns restrictions are working 7 | spec: 8 | schedule: "*/1 * * * *" 9 | rules: 10 | - name: approved-dns-host-lookup-should-work 11 | type: dns 12 | host: github.com 13 | expected: pass 14 | validate: 15 | message: DNS requests to an approved host. 16 | - name: approved-dns-host-subdomain-lookup-should-work 17 | type: dns 18 | host: status.github.com 19 | expected: pass 20 | validate: 21 | message: DNS requests for a subdomain of an approved host. 22 | - name: k8s-svc-dns-lookup-should-work 23 | type: dns 24 | host: kubernetes.default 25 | expected: pass 26 | validate: 27 | message: DNS lookup of the kubernetes service with namespace should work. 28 | - name: k8s-svc-dns-lookup-should-work 29 | type: dns 30 | host: kubernetes.default.svc 31 | expected: pass 32 | validate: 33 | message: DNS lookup of the kubernetes service should work. 34 | - name: k8s-svc-with-cluster-domain-lookup-should-work 35 | type: dns 36 | host: kubernetes.default.svc.cluster.local 37 | expected: pass 38 | validate: 39 | message: DNS lookup of the kubernetes service should work. 40 | -------------------------------------------------------------------------------- /docs/src/pages/docs/writing-plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Writing plugins 3 | description: How to write a plugin for Netchecks 4 | --- 5 | 6 | 7 | {% callout type="warning" title="Not Implemented Yet" %} 8 | 9 | The plugin architecture is still under design. Please get in touch if you'd like to help. 10 | 11 | {% /callout %} 12 | 13 | ## Plugin goals 14 | 15 | Netchecks will provide a plugin framework that allows: 16 | 17 | - extending the set of network assertions with new types of checks. 18 | 19 | 20 | Currently, the Netcheck probe command line tool is written in Python. The plugin framework will allow writing 21 | assertions in other languages such as Rust, Go, or C. 22 | 23 | There are a couple of options for installing plugins in the Netcheck probe. The first is to install the 24 | plugin as a Python package in the main distributed container before running any assertions - although this 25 | assumes the Python package (e.g. on PyPi) can be reached. The second is to install the plugin in a separate 26 | Docker image. 27 | 28 | Likely users will be able to build their own Docker images that build upon the Netcheck probe 29 | base image adding plugins for custom assertions. 30 | 31 | The operator needs to be know to use the appropriate Docker image - this could be done by: 32 | - configuring the default probe image for all NetworkAssertions during operator installation, or 33 | - configuring the probe image for each NetworkAssertion individually. 34 | 35 | -------------------------------------------------------------------------------- /docs/src/components/Prose.jsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | 3 | export function Prose({ as: Component = 'div', className, ...props }) { 4 | return ( 5 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /scripts/check_versions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pathlib import Path 4 | import sys 5 | import tomllib 6 | import yaml 7 | 8 | 9 | def read_toml(file: str) -> dict: 10 | with open(file, "rb") as f: 11 | return tomllib.load(f) 12 | 13 | 14 | def read_yaml(file: str) -> dict: 15 | with open(file, "r") as f: 16 | return yaml.safe_load(f) 17 | 18 | 19 | if __name__ == "__main__": 20 | git_root_path = Path(__file__).resolve().parent.parent 21 | cli_toml_path = git_root_path / "pyproject.toml" 22 | operator_toml_path = git_root_path / "operator" / "pyproject.toml" 23 | operator_chart_path = git_root_path / "operator" / "charts" / "netchecks" / "Chart.yaml" 24 | 25 | cli_toml = read_toml(cli_toml_path) 26 | operator_toml = read_toml(operator_toml_path) 27 | operator_chart_yaml = read_yaml(operator_chart_path) 28 | 29 | cli_version = cli_toml["project"]["version"] 30 | operator_version = operator_toml["tool"]["poetry"]["version"] 31 | operator_chart_version = operator_chart_yaml["appVersion"] 32 | 33 | if len(set((cli_version, operator_version, operator_chart_version))) == 1: 34 | print("Versions match!") 35 | sys.exit(0) 36 | else: 37 | print( 38 | f"Error: Versions do not match\n{cli_toml_path}: {cli_version}\n{operator_toml_path}: {operator_version}\n{operator_chart_path}: {operator_chart_version}", 39 | file=sys.stderr, 40 | ) 41 | sys.exit(1) 42 | -------------------------------------------------------------------------------- /operator/examples/cilium-dns-restrictions/cluster-dns-should-work.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: cluster-dns-should-work 5 | annotations: 6 | description: Check cluster dns behaviour 7 | spec: 8 | # Every 20 minutes 9 | schedule: "*/20 * * * *" 10 | rules: 11 | - name: external-dns-host-lookup-should-work 12 | type: dns 13 | host: github.com 14 | expected: pass 15 | validate: 16 | message: DNS lookup of an external host using default nameserver. 17 | - name: k8s-svc-dns-lookup-should-work 18 | type: dns 19 | host: kubernetes.default.svc 20 | expected: pass 21 | validate: 22 | message: DNS lookup of the kubernetes service should work. 23 | - name: k8s-svc-with-cluster-domain-lookup-should-work 24 | type: dns 25 | host: kubernetes.default.svc.cluster.local 26 | expected: pass 27 | validate: 28 | message: DNS lookup of the fqdn kubernetes service should work. 29 | - name: missing-svc-dns-lookup-should-fail 30 | type: dns 31 | host: unlikely-a-real-service.default.svc.cluster.local 32 | expected: fail 33 | validate: 34 | message: DNS lookup of the missing service should fail. 35 | - name: github-status-should-work 36 | type: http 37 | url: https://github.com/status 38 | validate: 39 | message: Http request to github status API should succeed. 40 | pattern: "data.body.contains('GitHub lives!') && data['status-code'] in [200, 201]" 41 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pytest import fixture 4 | 5 | TEST_DATA_DIR = os.path.join( 6 | os.path.dirname(os.path.realpath(__file__)), 7 | "testdata", 8 | ) 9 | 10 | 11 | @fixture() 12 | def simple_config_filename(): 13 | return os.path.join(TEST_DATA_DIR, "simple-config.json") 14 | 15 | 16 | @fixture() 17 | def dns_config_filename(): 18 | return os.path.join(TEST_DATA_DIR, "dns-config.json") 19 | 20 | 21 | @fixture() 22 | def internal_config_filename(): 23 | return os.path.join(TEST_DATA_DIR, "internal-check-config.json") 24 | 25 | 26 | @fixture() 27 | def dns_config_with_validation_filename(): 28 | return os.path.join(TEST_DATA_DIR, "dns-config-custom-validation.json") 29 | 30 | 31 | @fixture() 32 | def config_with_context_filename(): 33 | return os.path.join(TEST_DATA_DIR, "config-with-context.json") 34 | 35 | 36 | @fixture() 37 | def data_filename(): 38 | return os.path.join(TEST_DATA_DIR, "data.json") 39 | 40 | 41 | @fixture() 42 | def data_dir_path(): 43 | return os.path.join(TEST_DATA_DIR, "dir-of-data") 44 | 45 | 46 | @fixture() 47 | def invalid_config_filename(): 48 | return os.path.join(TEST_DATA_DIR, "invalid-config-unknown-check.json") 49 | 50 | 51 | @fixture() 52 | def valid_config_expected_fail_filename(): 53 | return os.path.join(TEST_DATA_DIR, "simple-config-with-expected-failures.json") 54 | 55 | 56 | @fixture() 57 | def valid_config_unexpected_fail_filename(): 58 | return os.path.join(TEST_DATA_DIR, "simple-config-with-unexpected-failures.json") 59 | 60 | 61 | @fixture() 62 | def http_headers_config_filename(): 63 | return os.path.join(TEST_DATA_DIR, "http-with-headers.json") 64 | -------------------------------------------------------------------------------- /operator/examples/policy-reporter/README.md: -------------------------------------------------------------------------------- 1 | 2 | This example deploys the Kyverno Policy Reporter to demonstrate the metrics and alerting 3 | capabilities. 4 | 5 | See https://kyverno.github.io/policy-reporter/ for detailed configuration of Policy Reporter. 6 | 7 | ## Install the Policy Reporter 8 | 9 | ```shell 10 | # Create the policy-reporter namespace if it doesn't exist 11 | kubectl create namespace policy-reporter 12 | kubectl apply -f https://github.com/kyverno/policy-reporter/raw/main/manifest/policy-reporter/install.yaml 13 | ``` 14 | 15 | ## Install Prometheus and Grafana 16 | 17 | ### Add the helm repository for Prometheus and Grafana 18 | 19 | ```shell 20 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 21 | helm repo add grafana https://grafana.github.io/helm-charts 22 | helm repo update 23 | ``` 24 | 25 | ### Install Prometheus 26 | 27 | From the root directory of this repository 28 | 29 | ```shell 30 | helm upgrade --install prometheus operator/prometheus-community/prometheus --values examples/policy-reporter/prometheus-values.yaml 31 | ``` 32 | 33 | ### Access Prometheus UI 34 | 35 | ```shell 36 | export POD_NAME=$(kubectl get pods --namespace default -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}") 37 | kubectl --namespace default port-forward $POD_NAME 9090 38 | ``` 39 | 40 | ### Explore the Policy Reporter metrics 41 | 42 | For example show the results by status: 43 | 44 | `sum by (status) (policy_report_summary{namespace='default'})` 45 | 46 | ![prometheus-metrics-screenshot.png](prometheus-metrics-screenshot.png) 47 | 48 | 49 | ## Alert Manager 50 | 51 | ![slack-alert.png](slack-alert.png) 52 | -------------------------------------------------------------------------------- /operator/examples/policy-reporter/prometheus-values.yaml: -------------------------------------------------------------------------------- 1 | alertmanagerFiles: 2 | alertmanager.yml: 3 | global: 4 | slack_api_url: '' 5 | 6 | route: 7 | receiver: 'slack-notifications' 8 | 9 | receivers: 10 | - name: 'slack-notifications' 11 | slack_configs: 12 | - channel: '#test-notifications' 13 | send_resolved: true 14 | title: |- 15 | Policy Failing [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.namespace }} 16 | text: >- 17 | {{ range .Alerts -}} 18 | *Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - `{{ .Labels.severity }}`{{ end }} 19 | 20 | *Description:* {{ .Annotations.description }} 21 | 22 | *Details:* 23 | {{ range .Labels.SortedPairs }} • *{{ .Name }}:* `{{ .Value }}` 24 | {{ end }} 25 | {{ end }} 26 | 27 | ## Prometheus server ConfigMap entries 28 | ## 29 | serverFiles: 30 | ## Alerts configuration 31 | ## Ref: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ 32 | ## Ref: https://awesome-prometheus-alerts.grep.to/rules.html 33 | alerting_rules.yml: 34 | groups: 35 | 36 | - name: Netcheck 37 | rules: 38 | - alert: FailingPolicy 39 | expr: policy_report_summary{status='Fail'} != 0 40 | for: 1m 41 | labels: 42 | severity: warning 43 | annotations: 44 | summary: Policy is failing (instance {{ $labels.instance }}) 45 | description: "Policy {{ $labels.name }} is Failing.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}" 46 | -------------------------------------------------------------------------------- /operator/examples/cilium-cluster-wide-dns-restrictions/cluster-dns-should-work.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netchecks.io/v1 2 | kind: NetworkAssertion 3 | metadata: 4 | name: cluster-dns-should-work 5 | namespace: default 6 | annotations: 7 | description: Check normal cluster dns behaviour 8 | spec: 9 | # Every 20 minutes 10 | schedule: "*/2 * * * *" 11 | rules: 12 | - name: external-dns-host-lookup-should-work 13 | type: dns 14 | host: github.com 15 | expected: pass 16 | validate: 17 | message: DNS lookup of an external host using default nameserver. 18 | - name: approved-dns-host-subdomain-lookup-should-work 19 | type: dns 20 | host: status.github.com 21 | expected: pass 22 | validate: 23 | message: DNS requests for a subdomain of an external host. 24 | - name: internal-k8s-service-dns-lookup-should-work 25 | type: dns 26 | host: kubernetes 27 | expected: pass 28 | validate: 29 | message: DNS lookup of the kubernetes service should work. 30 | - name: k8s-svc-dns-lookup-should-work 31 | type: dns 32 | host: kubernetes.default.svc 33 | expected: pass 34 | validate: 35 | message: DNS lookup of the kubernetes service should work. 36 | - name: k8s-svc-with-cluster-domain-lookup-should-work 37 | type: dns 38 | host: kubernetes.default.svc.cluster.local 39 | expected: pass 40 | validate: 41 | message: DNS lookup of the fqdn kubernetes service should work. 42 | - name: missing-svc-dns-lookup-should-fail 43 | type: dns 44 | host: unlikely-a-real-service.default.svc.cluster.local 45 | expected: fail 46 | validate: 47 | message: DNS lookup of the missing service should fail. -------------------------------------------------------------------------------- /docs/src/components/Navigation.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { useRouter } from 'next/router' 3 | import clsx from 'clsx' 4 | 5 | export function Navigation({ navigation, className }) { 6 | let router = useRouter() 7 | 8 | return ( 9 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /docs/src/pages/docs/roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Roadmap 3 | description: Where is netchecks heading? 4 | --- 5 | 6 | {% callout type="note" title="Unstable" %} 7 | 8 | The project roadmap is still being defined. The following is a list of features that are likely to be considered for implementation in the near future. 9 | 10 | {% /callout %} 11 | 12 | ## Open Source 13 | 14 | 15 | - NetworkAssertion CRD validation and documentation. 16 | - Tracing support for better debugging. 17 | - Plugin architecture for supporting custom test types. 18 | - Full CI integration test suite across managed Kubernetes providers. 19 | - The option to run certain probes on matching Nodes via DaemonSets instead of CronJobs. 20 | - PolicyReporter plugin to better expose NetworkAssertion results. 21 | - Example of alert manager integration 22 | - Grafana dashboard 23 | 24 | ## Enterprise 25 | 26 | - Exporting reports 27 | - externally executed probes from hosted service. Run tests from the internet (can I access some internal service, locked down S3 bucket etc). UI for creating and managing tests. 28 | - Curated network assertions for standard enterprise policies. 29 | - Integration with enterprise security tools (e.g. Splunk, Sumo Logic, etc) 30 | - Is this S3 bucket accessible with this role? 31 | - Run on demand - after a change/release? 32 | - License available on AWS Marketplace and/or Github Marketplace 33 | - Hardened distro for enterprise. 34 | - Long term support/Service level agreements. 35 | - commercial plugin ecosystem could also be explored. 36 | - Operator dashboard 37 | 38 | ## Longer term 39 | 40 | - Using netchecks outside k8s - e.g. to test “Container as a service” and “Function as a service” systems. 41 | - Netchecks on the edge - e.g. to test IoT devices. 42 | - Netchecks on end user devices - e.g. to test mobile apps. 43 | -------------------------------------------------------------------------------- /docs/src/pages/_document.jsx: -------------------------------------------------------------------------------- 1 | import { Head, Html, Main, NextScript } from 'next/document' 2 | 3 | const themeScript = ` 4 | let isDarkMode = window.matchMedia('(prefers-color-scheme: dark)') 5 | 6 | function updateTheme(theme) { 7 | theme = theme ?? window.localStorage.theme ?? 'system' 8 | 9 | if (theme === 'dark' || (theme === 'system' && isDarkMode.matches)) { 10 | document.documentElement.classList.add('dark') 11 | } else if (theme === 'light' || (theme === 'system' && !isDarkMode.matches)) { 12 | document.documentElement.classList.remove('dark') 13 | } 14 | 15 | return theme 16 | } 17 | 18 | function updateThemeWithoutTransitions(theme) { 19 | updateTheme(theme) 20 | document.documentElement.classList.add('[&_*]:!transition-none') 21 | window.setTimeout(() => { 22 | document.documentElement.classList.remove('[&_*]:!transition-none') 23 | }, 0) 24 | } 25 | 26 | document.documentElement.setAttribute('data-theme', updateTheme()) 27 | 28 | new MutationObserver(([{ oldValue }]) => { 29 | let newValue = document.documentElement.getAttribute('data-theme') 30 | if (newValue !== oldValue) { 31 | try { 32 | window.localStorage.setItem('theme', newValue) 33 | } catch {} 34 | updateThemeWithoutTransitions(newValue) 35 | } 36 | }).observe(document.documentElement, { attributeFilter: ['data-theme'], attributeOldValue: true }) 37 | 38 | isDarkMode.addEventListener('change', () => updateThemeWithoutTransitions()) 39 | ` 40 | 41 | export default function Document() { 42 | return ( 43 | 44 | 45 |