diff --git a/.github/configs/chart-testing.yaml b/.github/configs/chart-testing.yaml new file mode 100644 index 00000000..e8ed2320 --- /dev/null +++ b/.github/configs/chart-testing.yaml @@ -0,0 +1,3 @@ +charts: + - ./helm/flowforge +validate-maintainers: false diff --git a/.github/workflows/helm-chart.yml b/.github/workflows/helm-chart.yml new file mode 100644 index 00000000..3c81dc24 --- /dev/null +++ b/.github/workflows/helm-chart.yml @@ -0,0 +1,139 @@ +name: Helm chart validation +on: + push: + branches: + - main + paths: + - 'helm/*' + - '.github/workflows/helm-chart.yml' + pull_request: + branches: + - main + paths: + - 'helm/*' + - '.github/workflows/helm-chart.yml' + +jobs: + lint: + name: Lint and install chart + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Helm + uses: azure/setup-helm@v3.5 + with: + version: v3.13.2 + + - name: Install Python + uses: actions/setup-python@v4.7.1 + with: + python-version: 3.9 + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.6.1 + + - name: Add bitami repo + run: helm repo add bitnami https://charts.bitnami.com/bitnami + + - name: Lint helm chart + run: | + ct lint --config ./.github/configs/chart-testing.yaml + + - name: Create kind cluster + uses: helm/kind-action@v1.8.0 + + - name: Label cluster nodes + run: | + for node in $(kubectl get nodes -o name); do + kubectl label --overwrite $node "role=management" + done + + - name: Run chart-testing (install) + run: ct install --config ./.github/configs/chart-testing.yaml + + validate: + name: Validate chart against kubernetes API + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Helm + uses: azure/setup-helm@v3.5 + with: + version: v3.13.2 + + - name: Create kind cluster + uses: helm/kind-action@v1.8.0 + + - name: Validate chart + run: | + helm template flowforge ./helm/flowforge --set forge.domain=example.com | kubectl apply --validate=true -f - + + scan: + name: Scan chart + runs-on: ubuntu-latest + permissions: + security-events: write + strategy: + fail-fast: false + matrix: + tool: + - checkov + # temporary disabled due to https://github.com/zegl/kube-score/issues/559 + # - kube-score + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Helm + uses: azure/setup-helm@v3.5 + with: + version: v3.13.2 + + - name: Template chart + run: | + helm template flowforge ./helm/flowforge --set forge.domain=example.com > ${{ github.workspace }}/templated_chart.yaml + + - name: Scan chart with checkov + if: matrix.tool == 'checkov' + uses: bridgecrewio/checkov-action@v12 + with: + directory: ${{ github.workspace }} + file: templated_chart.yaml + framework: kubernetes + output_format: cli,sarif + output_file_path: console,results.sarif + soft_fail: true + + - name: Install kube-score + # temporary disabled due to https://github.com/zegl/kube-score/issues/559 + if: false + uses: yokawasa/action-setup-kube-tools@v0.9.3 + with: + setup-tools: "kube-score" + kube-score: '1.17.0' + + - name: Scan chart with kube-score + # temporary disabled due to https://github.com/zegl/kube-score/issues/559 + if: false + continue-on-error: true + run: + kube-score score ${{ github.workspace }}/templated_chart.yaml --output-format sarif > results.sarif + + - name: "Upload SARIF file" + if: success() || failure() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif diff --git a/helm/flowforge/Chart.lock b/helm/flowforge/Chart.lock index 7f062e75..c10b7660 100644 --- a/helm/flowforge/Chart.lock +++ b/helm/flowforge/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 10.14.0 -digest: sha256:6d76d2bf60161ead8a4802b56a209720105e5007719aaf2679a59cf067538bbd -generated: "2021-12-29T20:24:10.223567318Z" + version: 11.9.13 +digest: sha256:ea11617d363e174146b33ceafb5e2072d36731c2446178a91e5e6f705d0db067 +generated: "2023-11-15T14:44:27.496269+01:00" diff --git a/helm/flowforge/Chart.yaml b/helm/flowforge/Chart.yaml index 243bc6c2..724397af 100644 --- a/helm/flowforge/Chart.yaml +++ b/helm/flowforge/Chart.yaml @@ -10,7 +10,7 @@ keywords: - node-red dependencies: - name: postgresql - version: 10.14.0 + version: 11.9.13 repository: https://charts.bitnami.com/bitnami condition: forge.localPostgresql maintainers: diff --git a/helm/flowforge/README.md b/helm/flowforge/README.md index 95340be9..480596e3 100644 --- a/helm/flowforge/README.md +++ b/helm/flowforge/README.md @@ -43,6 +43,9 @@ If using an external PostgreSQL Database you will need to create the database an - `forge.branding` Object holding branding inserts (default not set) - `forge.projectDeploymentTolerations` tolerations settings for Project instances. Default is `[]`. - `forge.clusterRole.name` custom name for the ClusterRole (default `create-pod`) + - `forge.resources` allows to configure [resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the core application container + - `forge.podSecurityContext` allows to configure [securityContext](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for the core application pod + note: `forge.projectSelector` and `forge.managementSelector` defaults mean that you must have at least 2 nodes in your cluster and they need to be labeled before installing. @@ -75,6 +78,8 @@ To use STMP to send email - `forge.broker.url` URL to access the broker from inside the cluster (default `mqtt://flowforge-broker.[namespace]:1883`) - `forge.broker.public_url` URL to access the broker from outside the cluster (default `ws://mqtt.[forge.domain]`, uses `wss://` if `forge.https` is `true`) - `forge.broker.affinity` allows to configure [affinity or anti-affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) for the broker pod + - `forge.broker.resources` allows to configure [resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the broker container + - `forge.broker.podSecurityContext` allows to configure [securityContext](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for the broker pod ### Telemetry @@ -122,6 +127,8 @@ Enables FlowForge Telemetry - `forge.fileStore.context.type` Choice of backends for Persistent Context `sequelize` - `forge.fileStore.context.options` Options to pass to Persistent Context Driver (See [file-server](https://github.com/flowforge/flowforge-file-server) for details) - `forge.fileStore.context.quota` Sets the maximum number of bytes that a project can store in Persistent Context (default `1048576`) +- `forge.fileStore.resources` allows to configure [resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the file-server container +- `forge.fileStore.podSecurityContext` allows to configure [securityContext](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for the flowforge-file pod ### Private Certificate Authority diff --git a/helm/flowforge/charts/postgresql-10.14.0.tgz b/helm/flowforge/charts/postgresql-10.14.0.tgz deleted file mode 100644 index e4676714..00000000 Binary files a/helm/flowforge/charts/postgresql-10.14.0.tgz and /dev/null differ diff --git a/helm/flowforge/charts/postgresql-11.9.13.tgz b/helm/flowforge/charts/postgresql-11.9.13.tgz new file mode 100644 index 00000000..2d3ac623 Binary files /dev/null and b/helm/flowforge/charts/postgresql-11.9.13.tgz differ diff --git a/helm/flowforge/ci/ci-values.yaml b/helm/flowforge/ci/ci-values.yaml new file mode 100644 index 00000000..9d7b9515 --- /dev/null +++ b/helm/flowforge/ci/ci-values.yaml @@ -0,0 +1,75 @@ +forge: + dbUsername: forge + dbPassword: Zai1Wied + dbName: flowforge + localPostgresql: true + https: true + projectNamespace: flowforge + projectSelector: + role: projects + projectDeploymentTolerations: [] + # - key: purpose + # operator: Equal + # value: flowforge-projects + # effect: NoSchedule + managementSelector: + beta.kubernetes.io/os: linux + telemetry: + enabled: true + backend: + prometheus: + enabled: true + broker: + enabled: false + fileStore: + enabled: false + type: localfs + quota: 104857600 + options: + root: var/root + context: + type: sequelize + quota: 1048576 + options: + type: postgres + host: flowforge-postgresql + username: forge + password: Zai1Wied + database: ff-context + support: + enabled: false + + rate_limits: + enabled: false + global: true + max: 1000 + timeWindow: 60000 + + domain: "flowfuse-ci.com" + entryPoint: "lint" + environment: {} + image: "" + registry: "" + + contentSecurityPolicy: + enabled: false + reportOnly: false + +postgresql: + auth: + postgresPassword: Moomiet0 + username: forge + password: Zai1Wied + database: flowforge + global: + storageClass: standard + +ingress: + annotations: {} + className: "" + +editors: + serviceAccount: + create: true + annotations: {} + name: editors diff --git a/helm/flowforge/ci/default-values.yaml b/helm/flowforge/ci/default-values.yaml new file mode 100644 index 00000000..ac8aae50 --- /dev/null +++ b/helm/flowforge/ci/default-values.yaml @@ -0,0 +1,2 @@ +forge: + domain: example.com diff --git a/helm/flowforge/templates/broker.yaml b/helm/flowforge/templates/broker.yaml index 349889aa..6a36ec22 100644 --- a/helm/flowforge/templates/broker.yaml +++ b/helm/flowforge/templates/broker.yaml @@ -60,9 +60,7 @@ spec: app: flowforge-broker spec: securityContext: - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 + {{- toYaml .Values.forge.broker.podSecurityContext | nindent 8 }} containers: - name: broker image: iegomez/mosquitto-go-auth @@ -88,6 +86,9 @@ spec: # httpGet: # path: /ping.html # port: 1884 + {{- if .Values.forge.broker.resources }} + resources: {{- toYaml .Values.forge.broker.resources | nindent 12 }} + {{- end }} {{- if .Values.forge.registrySecrets }} imagePullSecrets: {{- range .Values.forge.registrySecrets }} diff --git a/helm/flowforge/templates/deployment.yaml b/helm/flowforge/templates/deployment.yaml index 6da28bd3..241e12a8 100644 --- a/helm/flowforge/templates/deployment.yaml +++ b/helm/flowforge/templates/deployment.yaml @@ -22,9 +22,7 @@ spec: spec: serviceAccountName: flowforge securityContext: - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 + {{- toYaml .Values.forge.podSecurityContext | nindent 8 }} containers: - name: forge {{- if .Values.forge.image }} @@ -77,6 +75,9 @@ spec: securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true + {{- if .Values.forge.resources }} + resources: {{- toYaml .Values.forge.resources | nindent 12 }} + {{- end }} {{- if .Values.forge.registrySecrets }} imagePullSecrets: {{- range .Values.forge.registrySecrets }} diff --git a/helm/flowforge/templates/file-storage.yml b/helm/flowforge/templates/file-storage.yml index da233a7a..3be75ede 100644 --- a/helm/flowforge/templates/file-storage.yml +++ b/helm/flowforge/templates/file-storage.yml @@ -61,9 +61,7 @@ spec: app: flowforge-file spec: securityContext: - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 + {{- toYaml .Values.forge.fileStore.podSecurityContext | nindent 8 }} containers: - name: file-storage image: {{ .Values.forge.registry }}{{- if .Values.forge.registry -}}/{{- end -}}flowforge/file-server:{{ .Chart.AppVersion }} @@ -83,6 +81,9 @@ spec: securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true + {{- if .Values.forge.fileStore.resources }} + resources: {{- toYaml .Values.forge.fileStore.resources | nindent 12 }} + {{- end }} {{- if .Values.forge.registrySecrets }} imagePullSecrets: {{- range .Values.forge.registrySecrets }} diff --git a/helm/flowforge/templates/job-upgrade-db.yaml b/helm/flowforge/templates/job-upgrade-db.yaml index 10190368..2b133055 100644 --- a/helm/flowforge/templates/job-upgrade-db.yaml +++ b/helm/flowforge/templates/job-upgrade-db.yaml @@ -38,7 +38,7 @@ spec: valueFrom: secretKeyRef: name: {{ .Release.Name }}-postgresql - key: postgresql-postgres-password + key: postgres-password volumeMounts: - name: upgrade-script mountPath: /usr/local diff --git a/helm/flowforge/values.schema.json b/helm/flowforge/values.schema.json index acf57f19..06dee1a0 100644 --- a/helm/flowforge/values.schema.json +++ b/helm/flowforge/values.schema.json @@ -218,6 +218,58 @@ }, "affinity": { "type": "object" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": ["number","string"] + }, + "memory": { + "type": ["number","string"] + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": ["number","string"] + }, + "memory": { + "type": ["number","string"] + } + } + } + } + }, + "podSecurityContext": { + "type": "object", + "properties": { + "runAsUser": { + "type": "integer" + }, + "runAsGroup": { + "type": "integer" + }, + "fsGroup": { + "type": "integer" + }, + "seccompProfile": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "localhostProfile": { + "type": "string" + } + } + } + } } }, "required": [ @@ -293,6 +345,58 @@ }, "options": { "type": "object" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": ["number","string"] + }, + "memory": { + "type": ["number","string"] + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": ["number","string"] + }, + "memory": { + "type": ["number","string"] + } + } + } + } + }, + "podSecurityContext": { + "type": "object", + "properties": { + "runAsUser": { + "type": "integer" + }, + "runAsGroup": { + "type": "integer" + }, + "fsGroup": { + "type": "integer" + }, + "seccompProfile": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "localhostProfile": { + "type": "string" + } + } + } + } } }, "required": [ @@ -383,7 +487,60 @@ "description": "Name of cluster role" } } + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": ["number","string"] + }, + "memory": { + "type": ["number","string"] + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": ["number","string"] + }, + "memory": { + "type": ["number","string"] + } + } + } + } + }, + "podSecurityContext": { + "type": "object", + "properties": { + "runAsUser": { + "type": "integer" + }, + "runAsGroup": { + "type": "integer" + }, + "fsGroup": { + "type": "integer" + }, + "seccompProfile": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "localhostProfile": { + "type": "string" + } + } + } + } } + }, "required": [ "domain", diff --git a/helm/flowforge/values.yaml b/helm/flowforge/values.yaml index 636abe8a..edf0602b 100644 --- a/helm/flowforge/values.yaml +++ b/helm/flowforge/values.yaml @@ -21,6 +21,12 @@ forge: enabled: false broker: enabled: false + podSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault fileStore: enabled: false type: localfs @@ -36,6 +42,12 @@ forge: username: forge password: Zai1Wied database: ff-context + podSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault support: enabled: false @@ -55,11 +67,19 @@ forge: enabled: false reportOnly: false + podSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + postgresql: - postgresqlPostgresPassword: Moomiet0 - postgresqlUsername: forge - postgresqlPassword: Zai1Wied - postgresqlDatabase: flowforge + auth: + postgresPassword: Moomiet0 + username: forge + password: Zai1Wied + database: flowforge ingress: annotations: {} @@ -69,4 +89,4 @@ editors: serviceAccount: create: true annotations: {} - name: editors \ No newline at end of file + name: editors