diff --git a/Makefile b/Makefile index f4ce72d..5b6426c 100644 --- a/Makefile +++ b/Makefile @@ -5,4 +5,7 @@ lint: test: ct install --config charts/ct.yaml -.PHONY: lint test \ No newline at end of file +docs: + bash ./chart-docs.sh + +.PHONY: lint test docs diff --git a/README.md b/README.md index 734bbdf..feb0709 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,11 @@ If you happen to stumble upon this repo, please be aware that this is a work in Also, it's a passion project, so please bear with me. -## Features / TODO +## Features -1. Supports running on a single instance with the DB split per mode. -2. Supports running multiple instances. +The chart supports [distributed deployment](https://quay.github.io/clair/howto/deployment.html#distributed-deployment): + +![Distributed deployment](https://quay.github.io/clair/howto/clairv4_distributed_multi_db.png) ## Usage @@ -25,7 +26,7 @@ Add the repository: helm repo add clair https://guerzon.github.io/clair-helm ``` -List the chart: +Verify: ```bash helm search repo clair @@ -39,9 +40,12 @@ If not using an ingresss: ```bash kubectl -n clair port-forward service/clair 6060:6060 +``` -## Test the vaultwarden/server image: -clairctl report --host http://localhost:6060 vaultwarden/server +Test the vaultwarden/server image: + +```bash +clairctl report ubuntu:focal ``` ## References diff --git a/charts/clair/Chart.yaml b/charts/clair/Chart.yaml index 54bc9b2..28df8a2 100644 --- a/charts/clair/Chart.yaml +++ b/charts/clair/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.0.3 +version: 0.1.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/clair/templates/_helpers.tpl b/charts/clair/templates/_helpers.tpl index c5d177f..b2d45c5 100644 --- a/charts/clair/templates/_helpers.tpl +++ b/charts/clair/templates/_helpers.tpl @@ -35,7 +35,6 @@ Common labels */}} {{- define "clair.labels" -}} helm.sh/chart: {{ include "clair.chart" . }} -{{ include "clair.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -45,8 +44,16 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Selector labels */}} -{{- define "clair.selectorLabels" -}} -app.kubernetes.io/name: {{ include "clair.name" . }} +{{- define "clair.selectorLabelsIndexer" -}} +app.kubernetes.io/name: {{ include "clair.name" . }}-indexer +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} +{{- define "clair.selectorLabelsMatcher" -}} +app.kubernetes.io/name: {{ include "clair.name" . }}-matcher +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} +{{- define "clair.selectorLabelsNotifier" -}} +app.kubernetes.io/name: {{ include "clair.name" . }}-notifier app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} diff --git a/charts/clair/templates/configmap.yaml b/charts/clair/templates/configmap.yaml index 03c3c5a..6de76ce 100644 --- a/charts/clair/templates/configmap.yaml +++ b/charts/clair/templates/configmap.yaml @@ -5,34 +5,79 @@ metadata: data: config.yaml: |- --- - http_listen_addr: ":{{ .Values.service.port }}" - introspection_addr: ":{{ .Values.introspectionService.port }}" + http_listen_addr: "{{ .Values.http.address }}:{{ .Values.http.port }}" + introspection_addr: "{{ .Values.introspection.address }}:{{ .Values.introspection.port }}" + {{- if .Values.auth }} + auth: + {{- toYaml .Values.auth | nindent 6 }} + {{- end }} log_level: "{{ .Values.logLevel }}" indexer: - connstring: "host={{ .Values.database.host }} user={{ .Values.database.user }} password={{ .Values.database.password }} dbname={{ .Values.database.dbName }} sslmode={{ .Values.database.sslMode }}" - scanlock_retry: 10 - layer_scan_concurrency: 5 - migrations: true + {{- if .Values.database.indexer.uriOverride }} + connstring: {{ .Values.database.indexer.uriOverride | quote }} + {{- else }} + connstring: "host={{ .Values.database.indexer.host }} user={{ .Values.database.indexer.user }} password={{ .Values.database.indexer.password }} dbname={{ .Values.database.indexer.name }} sslmode={{ .Values.database.indexer.sslMode }}" + {{- end }} + scanlock_retry: {{ .Values.indexer.scanLockRetry}} + layer_scan_concurrency: {{ .Values.indexer.layerScanConcurrency }} + migrations: {{ .Values.indexer.migrations }} + airgap: {{ .Values.indexer.airgap }} matcher: - connstring: "host={{ .Values.database.host }} user={{ .Values.database.user }} password={{ .Values.database.password }} dbname={{ .Values.database.dbName }} sslmode={{ .Values.database.sslMode }}" - indexer_addr: "http://localhost:{{ .Values.service.port }}/" - migrations: true - max_conn_pool: 100 - matchers: {} + {{- if .Values.database.matcher.uriOverride }} + connstring: {{ .Values.database.matcher.uriOverride | quote }} + {{- else }} + connstring: "host={{ .Values.database.matcher.host }} user={{ .Values.database.matcher.user }} password={{ .Values.database.matcher.password }} dbname={{ .Values.database.matcher.name }} sslmode={{ .Values.database.matcher.sslMode }}" + {{- end }} + indexer_addr: {{ .Values.notifier.indexerAddress | quote }} + migrations: {{ .Values.matcher.migrations }} + period: {{ .Values.matcher.period | quote }} + disable_updaters: {{ .Values.matcher.disableUpdaters }} + update_retention: {{ .Values.matcher.updateRetention }} + {{- if .Values.matchers }} + matchers: + names: + {{- toYaml .Values.matchers.names | nindent 8 }} + {{- if .Values.matchers.config }} + config: + {{- toYaml .Values.matchers.config | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.updaters }} updaters: sets: - - ubuntu - - debian - - rhel - - alpine - - osv + {{- toYaml .Values.updaters.sets | nindent 8 }} + {{- if .Values.updaters.config }} + config: + {{- toYaml .Values.updaters.config | nindent 8 }} + {{- end }} + {{- end }} notifier: - connstring: "host={{ .Values.database.host }} user={{ .Values.database.user }} password={{ .Values.database.password }} dbname={{ .Values.database.dbName }} sslmode={{ .Values.database.sslMode }}" - migrations: true - indexer_addr: "http://localhost:{{ .Values.service.port }}/" - matcher_addr: "http://localhost:{{ .Values.service.port }}/" - poll_interval: "1m" - delivery_interval: "30s" - disable_summary: false + {{- if .Values.database.notifier.uriOverride }} + connstring: {{ .Values.database.notifier.uriOverride | quote }} + {{- else }} + connstring: "host={{ .Values.database.notifier.host }} user={{ .Values.database.notifier.user }} password={{ .Values.database.notifier.password }} dbname={{ .Values.database.notifier.name }} sslmode={{ .Values.database.notifier.sslMode }}" + {{- end }} + migrations: {{ .Values.notifier.migrations }} + indexer_addr: {{ .Values.notifier.indexerAddress | quote }} + matcher_addr: {{ .Values.notifier.matcherAddress | quote }} + poll_interval: {{ .Values.notifier.pollInterval | quote }} + delivery_interval: {{ .Values.notifier.deliveryInterval | quote }} + disable_summary: {{ .Values.notifier.disableSummary }} + {{- if .Values.notifier.webhook }} + webhook: + {{- toYaml .Values.notifier.webhook | nindent 10 }} + {{- end }} + {{- if .Values.notifier.amqp }} + amqp: + {{- toYaml .Values.notifier.amqp | nindent 10 }} + {{- end }} + {{- if .Values.notifier.stomp }} + stomp: + {{- toYaml .Values.notifier.stomp | nindent 10 }} + {{- end }} + {{- if .Values.trace }} + trace: + {{- toYaml .Values.trace | nindent 6 }} + {{- end }} metrics: name: "prometheus" diff --git a/charts/clair/templates/deployment.yaml b/charts/clair/templates/deployment.yaml index 4d38cfa..f9863ae 100644 --- a/charts/clair/templates/deployment.yaml +++ b/charts/clair/templates/deployment.yaml @@ -1,16 +1,17 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "clair.fullname" . }} + name: {{ include "clair.fullname" . }}-indexer labels: {{- include "clair.labels" . | nindent 4 }} + {{- include "clair.selectorLabelsIndexer" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: - {{- include "clair.selectorLabels" . | nindent 6 }} + {{- include "clair.selectorLabelsIndexer" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} @@ -19,6 +20,7 @@ spec: {{- end }} labels: {{- include "clair.labels" . | nindent 8 }} + {{- include "clair.selectorLabelsIndexer" . | nindent 8 }} {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} @@ -38,19 +40,166 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http - containerPort: {{ .Values.service.port }} + containerPort: {{ .Values.http.port }} protocol: TCP - name: introspection - containerPort: {{ .Values.introspectionService.port }} + containerPort: {{ .Values.introspection.port }} protocol: TCP + env: + - name: CLAIR_MODE + value: "indexer" livenessProbe: httpGet: path: /healthz - port: {{ .Values.introspectionService.port }} + port: {{ .Values.introspection.port }} + initialDelaySeconds: 10 readinessProbe: httpGet: path: /readyz - port: {{ .Values.introspectionService.port }} + port: {{ .Values.introspection.port }} + initialDelaySeconds: 10 + periodSeconds: 5 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "clair.fullname" . }}-matcher + labels: + {{- include "clair.labels" . | nindent 4 }} + {{- include "clair.selectorLabelsMatcher" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "clair.selectorLabelsMatcher" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "clair.labels" . | nindent 8 }} + {{- include "clair.selectorLabelsMatcher" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "clair.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.http.port }} + protocol: TCP + env: + - name: CLAIR_MODE + value: "matcher" + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +--- +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "clair.fullname" . }}-notifier + labels: + {{- include "clair.labels" . | nindent 4 }} + {{- include "clair.selectorLabelsNotifier" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "clair.selectorLabelsNotifier" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "clair.labels" . | nindent 8 }} + {{- include "clair.selectorLabelsNotifier" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "clair.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.http.port }} + protocol: TCP + env: + - name: CLAIR_MODE + value: "notifier" resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.volumeMounts }} diff --git a/charts/clair/templates/service.yaml b/charts/clair/templates/service.yaml index c4e564b..070cab8 100644 --- a/charts/clair/templates/service.yaml +++ b/charts/clair/templates/service.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "clair.fullname" . }} + name: {{ include "clair.fullname" . }}-indexer labels: {{- include "clair.labels" . | nindent 4 }} spec: @@ -12,7 +12,39 @@ spec: protocol: TCP name: http selector: - {{- include "clair.selectorLabels" . | nindent 4 }} + {{- include "clair.selectorLabelsIndexer" . | nindent 4 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "clair.fullname" . }}-matcher + labels: + {{- include "clair.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "clair.selectorLabelsMatcher" . | nindent 4 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "clair.fullname" . }}-notifier + labels: + {{- include "clair.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "clair.selectorLabelsNotifier" . | nindent 4 }} --- apiVersion: v1 kind: Service @@ -28,4 +60,4 @@ spec: protocol: TCP name: introspection selector: - {{- include "clair.selectorLabels" . | nindent 4 }} + {{- include "clair.selectorLabelsIndexer" . | nindent 4 }} diff --git a/charts/clair/values.yaml b/charts/clair/values.yaml index 79ad1ad..91fd5ea 100644 --- a/charts/clair/values.yaml +++ b/charts/clair/values.yaml @@ -15,20 +15,100 @@ nameOverride: "" fullnameOverride: "" # Database Configuration -# this will be used for the indexer, notifier, and matcher databases database: - # Currently only postgresql is supported - type: "postgres" - host: "" - port: "5432" - # Database name - dbName: "" - user: "" - password: "" - uriOverride: "" - existingSecret: "" - existingSecretKey: "" - sslMode: "verify-full" + indexer: + # Override takes precedence over any of the succeeding configs. + # uriOverride: "host=clairdb user=pqgotest dbname=pqgotest sslmode=verify-full" + # uriOverride: "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full" + host: "" + port: "5432" + name: "" + user: "" + password: "" + sslMode: "verify-full" + # matcher: + # notifier: + +auth: {} +# auth: +# psk: +# key: 'c2VjcmV0' +# iss: +# - quay +# - clairctl + +# Indexer configuration +indexer: + scanLockRetry: 10 + layerScanConcurrency: 5 + migrations: true + scanner: {} + airgap: false + +matcher: + migrations: true + period: "6h" + disableUpdaters: false + updateRetention: 10 + +notifier: + migrations: true + indexerAddress: "http://clair-indexer:8080/" + matcherAddress: "http://clair-matcher:8080/" + pollInterval: "1m" + deliveryInterval: "30s" + disableSummary: false + webhook: {} + # target: "http://webhook-target/" + # callback: "http://clair-notifier:6060/notifier/api/v1/notification/" + amqp: {} + stomp: {} + +trace: {} +# trace: +# name: "jaeger" +# probability: 1 +# jaeger: +# agent: +# endpoint: "clair-jaeger:6831" +# service_name: "clair" + +matchers: {} +# matchers: +# names: +# - alpine +# - aws +# - debian +# - python +# - rhel +# - ubuntu +# config: +# python: +# ignore_vulns: +# - CVE-XYZ +# - CVE-ABC + +updaters: {} +# updaters: +# sets: +# - alpine +# - aws +# - debian +# - rhel +# - ubuntu +# config: +# ubuntu: +# security_tracker_url: http://security.url +# ignore_distributions: +# - cosmic + +http: + address: "" + port: 6060 + +introspection: + address: "" + port: 8089 serviceAccount: # Specifies whether a service account should be created @@ -57,7 +137,7 @@ securityContext: {} service: type: ClusterIP - port: 6060 + port: 8080 introspectionService: type: ClusterIP diff --git a/demo.yaml b/demo.yaml index 92fa599..a66a199 100644 --- a/demo.yaml +++ b/demo.yaml @@ -1,15 +1,23 @@ -replicaCount: 2 - -autoscaling: - enabled: false - database: - host: "clairdb-svc.clairdb" - dbName: "appdb" - user: "app" - password: "Supers3cret" - sslMode: "disable" + indexer: + host: "clairdb-svc.clairdb" + name: "indexerdb" + user: "app" + password: "Supers3cret" + sslMode: "disable" + matcher: + host: "clairdb-svc.clairdb" + name: "matcherdb" + user: "app" + password: "Supers3cret" + sslMode: "disable" + notifier: + host: "clairdb-svc.clairdb" + name: "notifierdb" + user: "app" + password: "Supers3cret" + sslMode: "disable" logLevel: "debug-color" @@ -17,3 +25,66 @@ resources: requests: cpu: 100m memory: 128Mi + +notifier: + webhook: + target: "http://webhook-target/" + callback: "http://clair-notifier:6060/notifier/api/v1/notification/" + +# matchers: +# names: +# - alpine +# - aws +# - debian +# - oracle +# - photon +# - python +# - rhel +# - suse +# - ubuntu +# - crda +# config: +# python: +# ignore_vulns: +# - CVE-XYZ +# - CVE-ABC + +# updaters: +# sets: +# - alpine +# - aws +# - debian +# - oracle +# - photon +# - pyupio +# - rhel +# - suse +# - ubuntu +# config: +# ubuntu: +# security_tracker_url: http://security.url +# ignore_distributions: +# - cosmic + +# notifier: +# webhook: +# target: "http://webhook-target/" +# callback: "http://clair-notifier:6060/notifier/api/v1/notification/" +# amqp: +# direct: true +# exchange: +# name: "" +# type: "direct" +# durable: true +# auto_delete: false +# uris: ["amqp://guest:guest@clair-rabbitmq:5672/"] +# routing_key: "notifications" +# callback: "http://clair-notifier/notifier/api/v1/notifications" + +# trace: +# name: "jaeger" +# probability: 1 +# jaeger: +# agent: +# endpoint: "clair-jaeger:6831" +# service_name: "clair"