From 5535197a4888c821957742f6897d4915226ca570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= <57442769+gjedlicska@users.noreply.github.com> Date: Thu, 11 Aug 2022 13:49:47 +0200 Subject: [PATCH 01/14] gergo/pre commit (#906) * Adds hadolint as pre-commit * Addresses all hadolint comments * Hadolint docker works when entrypoint explicitly provided * Use noninteractive apt-get frontend and clean after install * build(circleci): filters for pre-commit should be same as for test-server * remove cache prefix as not currently necessary due to pre-commit-config.yaml changing * build(circleci): enable remote docker for pre-commit * build(circleci): use speckle pre-commit runner with built-in hadolint * build(server): dockerfile RUN statements are consolidated Each RUN statement in a Dockerfile creates a new layer. Hadolint rule DL3059 suggests they should be consolidated. * build(server): dockerfile RUN statements are consolidated Each RUN statement in a Dockerfile creates a new layer. Hadolint rule DL3059 suggests they should be consolidated. * Improve husky bash script to catch errors * Integrates pre-commit with husky * pre-commit should now be run by husky on every commit * pre-commit which requires additional installed dependencies is moved to separate file * Update README for revised developer instructions * Updates pre-commit yarn script * refactor(pre-commit): make everyone happy with loosly integrating husky and pre-commit scripts * chore(clean up pre-commit configs): clean some more pre-commit mess * chore(pre-commit): run pre-commit in ci too * fix(husky pre-commit): fix ci build husky invocation, the script is not commited to git * fix(circleci config): install yarn packages for linting in pre-commit * fix(pre-commit): fix shellcheck disable comment placement * fix(pre-commit): add shellcheck ignore * fix(pre-commit husyk): fix shellcheck ignore version Co-authored-by: Iain Sproat <68657+iainsproat@users.noreply.github.com> --- .circleci/config.yml | 19 +++++++++++--- .husky/pre-commit | 40 +++++++++++++++++++++--------- .pre-commit-config.deployment.yaml | 21 ---------------- .pre-commit-config.yaml | 37 +++++++++++---------------- package.json | 1 - 5 files changed, 57 insertions(+), 61 deletions(-) delete mode 100644 .pre-commit-config.deployment.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml index 1067eb9d9..bdbdeea29 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -211,12 +211,23 @@ jobs: key: cache-pre-commit-<>-{{ checksum "<>" }} paths: - ~/.cache/pre-commit + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: yarn + + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-{{ checksum "yarn.lock" }} + paths: + - .yarn/cache + - .yarn/unplugged - run: name: Run pre-commit - command: pre-commit run --all-files --config <> - - run: - name: Run deployment pre-commit - command: pre-commit run --all-files --config <> + command: ./.husky/pre-commit - run: command: git --no-pager diff name: git diff diff --git a/.husky/pre-commit b/.husky/pre-commit index 1363567bd..8cddd4ca6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,16 +1,32 @@ -#!/usr/bin/env bash -set -eo pipefail -[ -n "$CI" ] && exit 0 +#!/usr/bin/env sh +# shellcheck disable=SC1091 +set -e -#shellcheck source=/dev/null -. "$(dirname "$0")/_/husky.sh" +if [ -n "$CI" ] +then + echo "running eslint" + yarn lint + yarn prettier:check +else +# shellcheck disable=SC1090 + . "$(dirname "$0")/_/husky.sh" + yarn lint-staged +fi -yarn lint-staged -if ! command -v pre-commit &> /dev/null; then exit 0; fi -echo "πŸ” Detected pre-commit on this system. Running pre-commit checks..." -pre-commit run --all-files --config .pre-commit-config.yaml +echo "πŸ” looking for additional linter dependencies" -if ! command -v hadolint &> /dev/null || ! command -v helm &> /dev/null || ! command -v shellcheck &> /dev/null; then exit 0; fi -echo "πŸ” Detected additional dependencies (hadolint, helm, and shellcheck) on this system. Running additional pre-commit checks..." -pre-commit run --all-files --config .pre-commit-config.deployment.yaml +check_dependencies_available() { + for i in "${@}" + do + if ! command -v "${i}"; then + echo "No ${i} executable found skipping additional checks" >&2 + exit 0 + fi + done +} + +check_dependencies_available pre-commit hadolint helm shellcheck + +echo "All systems functional, running additional pre-commit checks..." +pre-commit run --all-files diff --git a/.pre-commit-config.deployment.yaml b/.pre-commit-config.deployment.yaml deleted file mode 100644 index 23f2a21aa..000000000 --- a/.pre-commit-config.deployment.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# pre-commit for deployment related resources -# e.g. shell files, dockerfiles, helm chart etc.. -repos: - - repo: https://github.com/hadolint/hadolint - rev: 'v2.10.0' - hooks: - - id: hadolint - - # Cannot use official repo as it relies on Docker, which cannot be supported by either pre-commit.ci or CircleCI - - repo: https://github.com/Jarmos-san/shellcheck-precommit - rev: 'v0.2.0' - hooks: - - id: shellcheck-system - - - repo: https://github.com/gruntwork-io/pre-commit - rev: 'v0.1.17' - hooks: - - id: helmlint - -ci: - autoupdate_schedule: quarterly diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aeda0f90d..9e7e912a6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,29 +1,20 @@ -# default pre-commit file, checks node.js code and basic file formatting +# pre-commit for deployment related resources +# e.g. shell files, dockerfiles, helm chart etc.. repos: - - repo: https://github.com/pre-commit/mirrors-eslint - rev: 'v8.19.0' # Use the sha / tag you want to point at + - repo: https://github.com/hadolint/hadolint + rev: 'v2.10.0' hooks: - - id: eslint - types: [file] - files: \.[jt]s$|vue$ # *.js, *.ts and vue - exclude: '(\/|^)((generated\/.*)|(\..*\.([jt]sx?|vue)))$' - args: - - '--max-warnings=0' - additional_dependencies: - - eslint@8.11.0 - - eslint-config-prettier@8.5.0 - - eslint-plugin-vue@8.5.0 - - '@babel/eslint-parser@7.18.2' - - '@babel/preset-env@t 7.16.11' - - '@typescript-eslint/eslint-plugin@5.21.0' - - '@typescript-eslint/parser@5.21.0' - - typescript@4.5.4 - - '@rushstack/eslint-patch@1.1.3' - - '@vue/eslint-config-typescript@11.0.0' + - id: hadolint + # Cannot use official repo as it relies on Docker, which cannot be supported by either pre-commit.ci or CircleCI + - repo: https://github.com/Jarmos-san/shellcheck-precommit + rev: 'v0.2.0' + hooks: + - id: shellcheck-system - - repo: https://github.com/pre-commit/mirrors-prettier - rev: 'v2.7.1' # Use the sha / tag you want to point at + - repo: https://github.com/gruntwork-io/pre-commit + rev: 'v0.1.17' hooks: - - id: prettier + - id: helmlint + ci: autoupdate_schedule: quarterly diff --git a/package.json b/package.json index 1907f4dd6..0bc20cff4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "lint": "eslint . --ext .js,.ts,.vue --max-warnings=0", "prettier:check": "prettier --check .", "prettier:fix": "prettier --write .", - "pre-commit": "pre-commit run --all-files --config .pre-commit-config.yaml && pre-commit run --all-files --config .pre-commit-config.deployment.yaml", "circleci:check": "circleci config validate ./.circleci/config.yml", "dev:docker:up": "docker-compose -f ./docker-compose-deps.yml -f ./docker-compose-dev.yml up -d", "dev:docker:down": "docker-compose -f ./docker-compose-deps.yml -f ./docker-compose-dev.yml down", From 6cbe32d8cd7b4c949114d16451a023c243b3bba4 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 10:33:27 +0100 Subject: [PATCH 02/14] GitHub template update (#921) * fix(pull request template): pR template should be the default and not an option - PR template was in a directory which allows selection using queries. The PR template should be provided by default so should be renamed and placed in the .github directory. - Remove obsolete template --- ...R_TEMPLATE.md => pull_request_template.md} | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md => pull_request_template.md} (74%) diff --git a/.github/PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md b/.github/pull_request_template.md similarity index 74% rename from .github/PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md rename to .github/pull_request_template.md index 637e5f616..1efeba6a8 100644 --- a/.github/PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md +++ b/.github/pull_request_template.md @@ -34,7 +34,7 @@ Connects #123 --> -## To-do before merge +## To-do before merge: @@ -84,3 +84,19 @@ addressed, and remove any items that are not relevant to this PR. - [ ] My code follows a similar style to existing code. - [ ] I have added appropriate tests. - [ ] I have updated or added relevant documentation. + +## References + + From 65a00dca2e9ce6708ed3211996ee5a81f2b575bd Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 13:20:19 +0100 Subject: [PATCH 03/14] feat(helm chart): add SecurityContext to pods and containers (#917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(helm chart): add SecurityContext to pods and containers Speckle pods should run with minimal privileges and capabilities to function. Fix https://github.com/specklesystems/speckle-server/issues/857 * Update securityContext for all pods * frontend runs as nonroot and readonly root filesystem - set fsgroup for all pods with volumes * Frontend requires write directory at /etc/nginx/conf.d * Allow openresty log directory to be writable * feat(helm local test): add test container into the make script Co-authored-by: GergΕ‘ Jedlicska --- .../nginx/templates/nginx.conf.template | 10 ++++- utils/helm/Makefile | 2 + .../fileimport_service/deployment.yml | 27 ++++++++++- .../templates/frontend/deployment.yml | 45 +++++++++++++++++++ .../templates/monitoring/deployment.yml | 18 ++++++++ .../templates/preview_service/deployment.yml | 27 ++++++++++- .../templates/server/deployment.yml | 32 +++++++++++-- .../templates/test/deployment.yml | 18 ++++++++ .../templates/webhook_service/deployment.yml | 27 ++++++++++- 9 files changed, 195 insertions(+), 11 deletions(-) diff --git a/packages/frontend/nginx/templates/nginx.conf.template b/packages/frontend/nginx/templates/nginx.conf.template index 2f72faac7..16264a8ec 100644 --- a/packages/frontend/nginx/templates/nginx.conf.template +++ b/packages/frontend/nginx/templates/nginx.conf.template @@ -56,11 +56,19 @@ set_real_ip_from 2a06:98c0::/29; real_ip_header CF-Connecting-IP; #real_ip_header X-Forwarded-For; - server { listen 80; client_max_body_size 100m; + # move default write paths to a custom directory + # kubernetes can mount this directory and prevent writes to the root directory + # https://github.com/openresty/docker-openresty/issues/119 + client_body_temp_path /var/run/openresty/nginx-client-body; + proxy_temp_path /var/run/openresty/nginx-proxy; + fastcgi_temp_path /var/run/openresty/nginx-fastcgi; + uwsgi_temp_path /var/run/openresty/nginx-uwsgi; + scgi_temp_path /var/run/openresty/nginx-scgi; + location / { root /usr/share/nginx/html; index app.html; diff --git a/utils/helm/Makefile b/utils/helm/Makefile index 8b54257d3..0e6d9a564 100644 --- a/utils/helm/Makefile +++ b/utils/helm/Makefile @@ -7,6 +7,7 @@ build: cd ../.. && docker build -t speckle/speckle-webhook-service:local -f packages/webhook-service/Dockerfile . cd ../.. && docker build -t speckle/speckle-fileimport-service:local -f packages/fileimport-service/Dockerfile . cd ../.. && docker build -t speckle/speckle-monitor-deployment:local -f utils/monitor-deployment/Dockerfile . + cd ../.. && docker build -t speckle/speckle-test-deployment:local -f utils/test-deployment/Dockerfile . echo "Making locally built images available inside minikube cluster. This takes a bit to copy, unfortunately..." @@ -16,6 +17,7 @@ build: minikube image load speckle/speckle-webhook-service:local minikube image load speckle/speckle-fileimport-service:local minikube image load speckle/speckle-monitor-deployment:local + minikube image load speckle/speckle-test-deployment:local install: diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index 3aada8c5f..64c3b758b 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -44,8 +44,20 @@ spec: cpu: {{ .Values.fileimport_service.limits.cpu }} memory: {{ .Values.fileimport_service.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} - name: postgres-certificate mountPath: /postgres-certificate {{- end }} @@ -84,10 +96,21 @@ spec: - name: FILE_IMPORT_TIME_LIMIT_MIN value: {{ .Values.fileimport_service.time_limit_min | quote }} + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + priorityClassName: low-priority - {{- if .Values.db.useCertificate }} volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate diff --git a/utils/helm/speckle-server/templates/frontend/deployment.yml b/utils/helm/speckle-server/templates/frontend/deployment.yml index 517c8bfb5..5138d14f6 100644 --- a/utils/helm/speckle-server/templates/frontend/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend/deployment.yml @@ -34,6 +34,18 @@ spec: cpu: {{ .Values.frontend.limits.cpu }} memory: {{ .Values.frontend.limits.memory }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + # Allow for k8s to remove the pod from the service endpoints to stop receive traffic lifecycle: preStop: @@ -51,4 +63,37 @@ spec: - name: FILE_SIZE_LIMIT_MB value: {{ .Values.file_size_limit_mb | quote }} + volumeMounts: + - mountPath: /var/cache/nginx + name: nginx-cache + - mountPath: /tmp/nginx + name: nginx-tmp + - mountPath: /etc/nginx/conf.d + name: nginx-confd + - mountPath: /usr/local/openresty/nginx/logs + name: openresty-logs + - mountPath: /var/run/openresty + name: openresty-tmp + priorityClassName: high-priority + + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + + volumes: + - name: nginx-cache + emptyDir: {} + - name: nginx-confd + emptyDir: {} + - name: nginx-tmp + emptyDir: {} + - name: openresty-logs + emptyDir: {} + - name: openresty-tmp + emptyDir: {} diff --git a/utils/helm/speckle-server/templates/monitoring/deployment.yml b/utils/helm/speckle-server/templates/monitoring/deployment.yml index fdcb37e98..3711838f8 100644 --- a/utils/helm/speckle-server/templates/monitoring/deployment.yml +++ b/utils/helm/speckle-server/templates/monitoring/deployment.yml @@ -34,6 +34,15 @@ spec: cpu: {{ .Values.monitoring.limits.cpu }} memory: {{ .Values.monitoring.limits.memory }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 {{- if .Values.db.useCertificate }} volumeMounts: @@ -55,6 +64,15 @@ spec: priorityClassName: low-priority + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + {{- if .Values.db.useCertificate }} volumes: - name: postgres-certificate diff --git a/utils/helm/speckle-server/templates/preview_service/deployment.yml b/utils/helm/speckle-server/templates/preview_service/deployment.yml index cd8799b43..2ce33c418 100644 --- a/utils/helm/speckle-server/templates/preview_service/deployment.yml +++ b/utils/helm/speckle-server/templates/preview_service/deployment.yml @@ -43,8 +43,20 @@ spec: cpu: {{ .Values.preview_service.limits.cpu }} memory: {{ .Values.preview_service.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} - name: postgres-certificate mountPath: /postgres-certificate {{- end }} @@ -66,11 +78,22 @@ spec: priorityClassName: low-priority + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + # Should be > preview generation time ( 1 hour for good measure ) terminationGracePeriodSeconds: 3600 - {{- if .Values.db.useCertificate }} volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index a6a3c8644..59b585251 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -34,10 +34,22 @@ spec: cpu: {{ .Values.server.limits.cpu }} memory: {{ .Values.server.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: - - name: postgres-certificate - mountPath: /postgres-certificate + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} + - name: postgres-certificate + mountPath: /postgres-certificate {{- end }} # Allow for k8s to remove the pod from the service endpoints to stop receive traffic @@ -242,9 +254,21 @@ spec: key: apollo_key {{- end }} priorityClassName: high-priority + + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 310 - {{- if .Values.db.useCertificate }} volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate diff --git a/utils/helm/speckle-server/templates/test/deployment.yml b/utils/helm/speckle-server/templates/test/deployment.yml index 6915fe5bf..ff72ec75a 100644 --- a/utils/helm/speckle-server/templates/test/deployment.yml +++ b/utils/helm/speckle-server/templates/test/deployment.yml @@ -24,5 +24,23 @@ spec: limits: cpu: {{ .Values.test.limits.cpu }} memory: {{ .Values.test.limits.memory }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + restartPolicy: Never + + securityContext: + runAsNonRoot: true + runAsUser: 20000 + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + {{- end }} diff --git a/utils/helm/speckle-server/templates/webhook_service/deployment.yml b/utils/helm/speckle-server/templates/webhook_service/deployment.yml index 898813a1c..9c090d879 100644 --- a/utils/helm/speckle-server/templates/webhook_service/deployment.yml +++ b/utils/helm/speckle-server/templates/webhook_service/deployment.yml @@ -43,8 +43,20 @@ spec: cpu: {{ .Values.webhook_service.limits.cpu }} memory: {{ .Values.webhook_service.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} - name: postgres-certificate mountPath: /postgres-certificate {{- end }} @@ -66,11 +78,22 @@ spec: priorityClassName: low-priority + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + # Should be > webhook max call time ( ~= 10 seconds ) terminationGracePeriodSeconds: 30 - {{- if .Values.db.useCertificate }} volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate From fb5631bd326b225108cd32e246921aa1a863e696 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 13:21:01 +0100 Subject: [PATCH 04/14] feat(helm chart): prometheus monitoring namespace and release name should be configurable (#914) * feat(helm chart): prometheus monitoring namespace and release name should be configurable Currently Speckle assumes prometheus is deployed in the 'speckle' namespace and is deployed as a release named 'kube-prometheus-stack'. This commit introduces non-breaking changes that allow custom values for these to be provided, defaulting to the current assumed values if they are not provided. fixes https://github.com/specklesystems/speckle-server/issues/863 * Fix serviceMonitor so that it can find services in a different namespace * Namespace selector is not required if the default namespace is being used --- .../speckle-server/templates/servicemonitor.yml | 15 +++++++++------ utils/helm/speckle-server/values.yaml | 3 +++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/utils/helm/speckle-server/templates/servicemonitor.yml b/utils/helm/speckle-server/templates/servicemonitor.yml index ea0b05471..748dec68b 100644 --- a/utils/helm/speckle-server/templates/servicemonitor.yml +++ b/utils/helm/speckle-server/templates/servicemonitor.yml @@ -1,19 +1,22 @@ -{{ if .Values.enable_prometheus_monitoring }} - +{{- if .Values.enable_prometheus_monitoring }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: speckle-server - namespace: {{ .Values.namespace }} + namespace: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} labels: app: speckle-server - release: kube-prometheus-stack + release: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }} {{ include "speckle.labels" . | indent 4 }} spec: selector: matchLabels: project: speckle-server +{{- if and .Values.prometheusMonitoring.namespace (ne .Values.namespace .Values.prometheusMonitoring.namespace) }} + namespaceSelector: + matchNames: + - {{ .Values.namespace }} +{{- end }} endpoints: - port: web - -{{ end }} +{{- end }} diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 4ddc5d2b1..d8dd4defe 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -124,6 +124,9 @@ test: secretName: server-vars enable_prometheus_monitoring: false +prometheusMonitoring: + namespace: '' + release: '' cert_manager_issuer: letsencrypt-staging helm_test_enabled: true From 0084102d0dbad8ba014978efd2260ba366601887 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 13:23:14 +0100 Subject: [PATCH 05/14] feat(helm chart): network policies are provided for all services (#909) * feat(helm chart): network policies are provided for all services Network policies are used to deny arbitrary egress and ingress to a pod, providing more security hardening. Fix https://github.com/specklesystems/speckle-server/issues/860 * NetworkPolicies for remaining services * Network policies are configurable but enabled by default * fix to naming * Use named port * Helper function for defining redis egress * Network policy is more tightly defined to port for service if fqdn * if an IP is provided for redis, postgres, or blob storage, egress is limited to that IP * Note about limitations * Simplifies networkpolicy logic by requiring variables to be provided in values.yaml * default disable networkpolicy, otherwise end users will have to provide all the additional values and that could become confusing * supports dependencies being deployed within the same cluster * Disable network policies by default * Ensure the host name does not contain a port * Exclude (likely) kubernetes IP ranges from allowed egress * Add explicit ingress to the server from fileimport and test * disable test networkpolicy if test is disabled * Allow egress to sentry * remove access to s3 from preview service * remove access to redis from fileimport service * Allow prometheus ingress to metrics endpoints * tightens ingress by restricting to the prometheus pod in a single namespace * Limit ingress on the server to the nginx ingress controller and prometheus * Limit ingress to frontend to just the nginx ingress controller * Fileimport does not require s3 --- .../speckle-server/templates/_helpers.tpl | 135 ++++++++++++++++++ .../fileimport_service/networkpolicy.yml | 45 ++++++ .../templates/frontend/networkpolicy.yml | 28 ++++ .../templates/monitoring/networkpolicy.yml | 38 +++++ .../preview_service/networkpolicy.yml | 38 +++++ .../templates/server/networkpolicy.yml | 75 ++++++++++ .../templates/test/networkpolicy.yml | 36 +++++ .../webhook_service/networkpolicy.yml | 42 ++++++ utils/helm/speckle-server/values.yaml | 50 ++++++- 9 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 utils/helm/speckle-server/templates/fileimport_service/networkpolicy.yml create mode 100644 utils/helm/speckle-server/templates/frontend/networkpolicy.yml create mode 100644 utils/helm/speckle-server/templates/monitoring/networkpolicy.yml create mode 100644 utils/helm/speckle-server/templates/preview_service/networkpolicy.yml create mode 100644 utils/helm/speckle-server/templates/server/networkpolicy.yml create mode 100644 utils/helm/speckle-server/templates/test/networkpolicy.yml create mode 100644 utils/helm/speckle-server/templates/webhook_service/networkpolicy.yml diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index b06b670e9..5927ac7ef 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -93,3 +93,138 @@ Part-of label {{- define "speckle.labels.part-of" -}} app.kubernetes.io/part-of: {{ include "speckle.name" . }} {{- end }} + +{{/* +Connects to kube api-server to determine if Cilium CRD are present. +If they are we assume that Cilium is installed. +*/}} +{{- define "speckle.networkpolicy.ciliumIsPresent" -}} + +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to Redis + +Expects the global context "$" to be passed as the parameter +*/}} +{{- define "speckle.networkpolicy.egress.redis" -}} +{{- $port := (default "6379" .Values.redis.networkPolicy.port ) -}} +{{- if .Values.redis.networkPolicy.inCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelector" .Values.redis.networkPolicy.inCluster.podSelector "namespaceSelector" .Values.redis.networkPolicy.inCluster.namespaceSelector "port" $port) }} +{{- else if .Values.redis.networkPolicy.externalToCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.external" (dict "ip" .Values.redis.networkPolicy.externalToCluster.ipv4 "port" $port) }} +{{- end -}} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to Postgres +*/}} +{{- define "speckle.networkpolicy.egress.postgres" -}} +{{- $port := (default "5432" .Values.db.networkPolicy.port ) -}} +{{- if .Values.db.networkPolicy.inCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelector" .Values.db.networkPolicy.inCluster.podSelector "namespaceSelector" .Values.db.networkPolicy.inCluster.namespaceSelector "port" $port) }} +{{- else if .Values.db.networkPolicy.externalToCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.external" (dict "ip" .Values.db.networkPolicy.externalToCluster.ipv4 "port" $port) }} +{{- end -}} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to Postgres +*/}} +{{- define "speckle.networkpolicy.egress.blob_storage" -}} +{{- $port := (default "443" .Values.s3.networkPolicy.port ) -}} +{{- if .Values.s3.networkPolicy.inCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelector" .Values.s3.networkPolicy.inCluster.podSelector "namespaceSelector" .Values.s3.networkPolicy.inCluster.namespaceSelector "port" $port) }} +{{- else if .Values.s3.networkPolicy.externalToCluster.enabled -}} + {{- $host := (urlParse .Values.s3.endpoint).host -}} + {{- if (contains ":" $host) -}} + {{- $host = first (mustRegexSplit ":" $host) -}} + {{- end -}} + {{- $ip := "" -}} + {{- if eq (include "speckle.isIPv4" $host) "true" -}} + {{- $ip = $host -}} + {{- end -}} +{{ include "speckle.networkpolicy.egress.external" (dict "ip" $ip "port" $port) }} +{{- end -}} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to an external url:port or ip:port + +Usage: +{{ include "speckle.networkpolicy.egress.external" (dict "ip" "" "port" "6379") }} + +Params: + - ip - String - Optional - If the IP is not known, then egress is allowed to 0.0.0.0/0. + - port - String - Required + +Limitations: + - IP is limited to IPv4 due to Kubernetes use of IPv4 CIDR + - Kubernetes network policies do not support FQDN, hence if IP is not known egress is allowed to 0.0.0.0/0 + +*/}} +{{- define "speckle.networkpolicy.egress.external" -}} +{{- if not .port -}} + {{- printf "\nNETWORKPOLICY ERROR: The port was not provided \"%s\"\n" .port | fail -}} +{{- end -}} +- to: + - ipBlock: + {{- if .ip }} + cidr: {{ printf "%s/32" .ip }} + {{- else }} + # Kubernetes network policy does not support fqdn, so we have to allow egress anywhere + cidr: 0.0.0.0/0 + # except to kubernetes pods or services + except: + - 10.0.0.0/8 + {{- end }} + ports: + - port: {{ printf "%s" .port }} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to a pod within the cluster + +Usage: +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelectorLabels" {matchLabels.name=redis} "namespaceSelector" {matchLabels.name=redis} "port" "6379") }} + +Params: + - podSelector - Object - Required + - namespaceSelector - Object - Required + - port - String - Required + +*/}} +{{- define "speckle.networkpolicy.egress.internal" -}} +{{- if not .podSelector -}} + {{- printf "\nNETWORKPOLICY ERROR: The pod selector was not provided\n" | fail -}} +{{- end -}} +{{- if not .namespaceSelector -}} + {{- printf "\nNETWORKPOLICY ERROR: The namespace selector was not provided\n" | fail -}} +{{- end -}} +{{- if not .port -}} + {{- printf "\nNETWORKPOLICY ERROR: The port was not provided \"%s\"\n" .port | fail -}} +{{- end -}} +- to: + - namespaceSelector: +{{ .namespaceSelector | toYaml | indent 8 }} + podSelector: +{{ .podSelector | toYaml | indent 8 }} + ports: + - port: {{ printf "%s" .port }} +{{- end }} + +{{/* +Tries to determine if a given string is a valid IP address +Usage: +{{ include "speckle.isIPv4" "123.45.67.89" }} + +Params: + - ip - String - Required - The string which we will try to determine is a valid IP address +*/}} +{{- define "speckle.isIPv4" -}} +{{- if regexMatch "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" . -}} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/fileimport_service/networkpolicy.yml b/utils/helm/speckle-server/templates/fileimport_service/networkpolicy.yml new file mode 100644 index 000000000..e7a92305b --- /dev/null +++ b/utils/helm/speckle-server/templates/fileimport_service/networkpolicy.yml @@ -0,0 +1,45 @@ +{{- if .Values.fileimport_service.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "fileimport_service.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "fileimport_service.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "fileimport_service.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP + # allow egress to speckle-server + - to: + - podSelector: + matchLabels: +{{ include "server.selectorLabels" $ | indent 16 }} + ports: + - port: 3000 + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/frontend/networkpolicy.yml b/utils/helm/speckle-server/templates/frontend/networkpolicy.yml new file mode 100644 index 000000000..57ba8114a --- /dev/null +++ b/utils/helm/speckle-server/templates/frontend/networkpolicy.yml @@ -0,0 +1,28 @@ +{{- if .Values.frontend.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "frontend.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "frontend.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "frontend.selectorLabels" . | indent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + # allow ingress from the loadbalancer + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.ingress.namespace }} + - podSelector: + matchLabels: + app.kubernetes.io/name: {{ .Values.ingress.controllerName }} + ports: + - port: www + egress: [] # block all egress +{{- end -}} diff --git a/utils/helm/speckle-server/templates/monitoring/networkpolicy.yml b/utils/helm/speckle-server/templates/monitoring/networkpolicy.yml new file mode 100644 index 000000000..307bd60fd --- /dev/null +++ b/utils/helm/speckle-server/templates/monitoring/networkpolicy.yml @@ -0,0 +1,38 @@ +{{- if .Values.monitoring.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "monitoring.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "monitoring.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "monitoring.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/preview_service/networkpolicy.yml b/utils/helm/speckle-server/templates/preview_service/networkpolicy.yml new file mode 100644 index 000000000..76ad67b9e --- /dev/null +++ b/utils/helm/speckle-server/templates/preview_service/networkpolicy.yml @@ -0,0 +1,38 @@ +{{- if .Values.preview_service.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "preview_service.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "preview_service.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "preview_service.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/server/networkpolicy.yml b/utils/helm/speckle-server/templates/server/networkpolicy.yml new file mode 100644 index 000000000..7bc6ed66e --- /dev/null +++ b/utils/helm/speckle-server/templates/server/networkpolicy.yml @@ -0,0 +1,75 @@ +{{- if .Values.server.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "server.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "server.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "server.selectorLabels" . | indent 6 }} + policyTypes: + - Ingress + - Egress + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP +{{- if .Values.server.sentry_dns }} + # sentry.io https://docs.sentry.io/product/security/ip-ranges/#event-ingestion + - to: + - ipBlock: + cidr: 34.120.195.249/32 + ports: + - port: 443 +{{- end }} + # redis +{{ include "speckle.networkpolicy.egress.redis" $ | indent 4 }} + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} + # blob storage +{{ include "speckle.networkpolicy.egress.blob_storage" $ | indent 4 }} + ingress: + # allow ingress from the loadbalancer + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.ingress.namespace }} + - podSelector: + matchLabels: + app.kubernetes.io/name: {{ .Values.ingress.controllerName }} + ports: + - port: http + # allow ingress from servicemonitor/prometheus + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: http + # allow ingress from the fileimport service + - from: + - podSelector: + matchLabels: +{{ include "fileimport_service.selectorLabels" $ | indent 14}} + ports: + - port: http +{{- if .Values.helm_test_enabled }} + # allow ingress from the test + - from: + - podSelector: + matchLabels: +{{ include "test.selectorLabels" $ | indent 14}} +{{- end }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/test/networkpolicy.yml b/utils/helm/speckle-server/templates/test/networkpolicy.yml new file mode 100644 index 000000000..be18a04e9 --- /dev/null +++ b/utils/helm/speckle-server/templates/test/networkpolicy.yml @@ -0,0 +1,36 @@ +{{- if and .Values.helm_test_enabled .Values.test.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "test.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "test.labels" $ | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "test.selectorLabels" $ | indent 6 }} + policyTypes: + - Ingress + - Egress + # all ingress is blocked + ingress: [] + + egress: + # allow egress to speckle-server + - to: + - podSelector: + matchLabels: +{{ include "server.selectorLabels" $ | indent 16 }} + ports: + - port: http + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP +{{- end -}} diff --git a/utils/helm/speckle-server/templates/webhook_service/networkpolicy.yml b/utils/helm/speckle-server/templates/webhook_service/networkpolicy.yml new file mode 100644 index 000000000..8055699c9 --- /dev/null +++ b/utils/helm/speckle-server/templates/webhook_service/networkpolicy.yml @@ -0,0 +1,42 @@ +{{- if .Values.webhook_service.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "webhook_service.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "webhook_service.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "webhook_service.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # webhook can call anything external, but is blocked from egress elsewhere within the cluster + - to: + - ipBlock: + cidr: 0.0.0.0/0 + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP +{{- end -}} diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index d8dd4defe..6597de9c9 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -11,6 +11,16 @@ db: maxConnectionsServer: 4 certificate: '' # Multi-line string with the contents of `ca-certificate.crt` PGSSLMODE: require + networkPolicy: # if network policy is enabled for any service, this provides the networkPolicy with the necessary details to allow egress connections to the database + port: '' # the port to connect to, if known (default: "5432") + externalToCluster: # use if the database is external to the kubernetes cluster + enabled: true # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed + host: '' # Domain name, or provide IP address. If both are provided IP address takes precedence + ipv4: '' # IP address of the externally hosted Database. If not known, provide the host instead. If both are provided the IP address takes precedence. + inCluster: # use if the database is deployed within the same kubernetes cluster as Speckle + enabled: false # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed + podSelector: {} # the selector to match with pod of the deployed database instance + namespaceSelector: {} # the selector to match the namespace in which the database pod is deployed s3: endpoint: '' @@ -18,9 +28,26 @@ s3: access_key: '' create_bucket: 'false' # secret_key: secret -> s3_secret_key + networkPolicy: # if network policy is enabled for any service, this provides the networkPolicy with the necessary details to allow egress connections to the s3 compatible storage + externalToCluster: # use if the s3 compatible storage is external to the kubernetes cluster + enabled: true # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed + inCluster: # use if the s3 compatible storage is deployed within the same kubernetes cluster as Speckle + enabled: false # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed + podSelector: {} # the selector to match with pod of the deployed s3 compatible storage instance + namespaceSelector: {} # the selector to match the namespace in which the s3 compatible storage pod is deployed -#redis: -# redis_url: secret -> redis_url +redis: + # redis_url: secret -> redis_url + networkPolicy: # if network policy is enabled for any service, this provides the networkPolicy with the necessary details to allow egress connections to redis + port: '' # the port to connect to, if known (default: "6379") + externalToCluster: # use if redis is external to the kubernetes cluster + enabled: true # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed + host: '' # Domain name of the externally hosted Redis. It is preferable to provide the IP address. If both are provided IP address takes precedence + ipv4: '' # IP address of the externally hosted Redis. If not known, provide the host instead. If both are provided the IP address takes precedence. + inCluster: # use if redis is deployed within the same kubernetes cluster as Speckle + enabled: false # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed + podSelector: {} # the selector to match with pod of the deployed Redis instance + namespaceSelector: {} # the selector to match the namespace in which the Redis pod is deployed server: replicas: 1 @@ -67,6 +94,9 @@ server: disable_tracking: false disable_tracing: false + networkPolicy: + enabled: false + frontend: replicas: 1 requests: @@ -75,6 +105,8 @@ frontend: limits: cpu: 1000m memory: 512Mi + networkPolicy: + enabled: false preview_service: replicas: 1 @@ -84,6 +116,8 @@ preview_service: limits: cpu: 1000m memory: 4Gi + networkPolicy: + enabled: false webhook_service: replicas: 1 @@ -93,6 +127,8 @@ webhook_service: limits: cpu: 200m memory: 512Mi + networkPolicy: + enabled: false fileimport_service: replicas: 1 @@ -103,6 +139,8 @@ fileimport_service: cpu: 1000m memory: 2Gi time_limit_min: 10 + networkPolicy: + enabled: false monitoring: replicas: 1 @@ -112,6 +150,8 @@ monitoring: limits: cpu: 200m memory: 512Mi + networkPolicy: + enabled: false test: requests: @@ -120,6 +160,8 @@ test: limits: cpu: 200m memory: 512Mi + networkPolicy: + enabled: false secretName: server-vars @@ -134,3 +176,7 @@ helm_test_enabled: true create_namespace: false file_size_limit_mb: 100 imagePullPolicy: IfNotPresent + +ingress: + namespace: ingress-nginx + controllerName: ingress-nginx From 72d27b9a7c115d7f7d5c9284745c4bb5069a33da Mon Sep 17 00:00:00 2001 From: Peter Grainger Date: Mon, 15 Aug 2022 13:24:30 +0100 Subject: [PATCH 06/14] Allow save object to S3 in different region (#910) * Allow save object to S3 in different region * feat(helm & docker-compose): adds S3_REGION to helm chart & docker-compose Explicitly adding the environment variable to deployment configuration files provides system operators with documentation of its existence. Set to empty by default, which will result in the default value being used. Co-authored-by: Iain Sproat <68657+iainsproat@users.noreply.github.com> --- .circleci/config.yml | 1 + docker-compose-speckle.yml | 2 ++ packages/server/modules/blobstorage/objectStorage.js | 2 +- utils/1click_image_scripts/template-docker-compose.yml | 2 ++ .../speckle-server/templates/fileimport_service/deployment.yml | 2 ++ utils/helm/speckle-server/templates/server/deployment.yml | 2 ++ utils/helm/speckle-server/values.yaml | 1 + 7 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bdbdeea29..28c6e8c90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -259,6 +259,7 @@ jobs: S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' S3_CREATE_BUCKET: 'true' + S3_REGION: '' # optional, defaults to 'us-east-1' steps: - checkout - restore_cache: diff --git a/docker-compose-speckle.yml b/docker-compose-speckle.yml index a090ccf17..5f1262bbe 100644 --- a/docker-compose-speckle.yml +++ b/docker-compose-speckle.yml @@ -39,6 +39,7 @@ services: S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' S3_CREATE_BUCKET: 'true' + S3_REGION: '' # optional, defaults to 'us-east-1' FILE_SIZE_LIMIT_MB: 100 preview-service: @@ -77,5 +78,6 @@ services: S3_ACCESS_KEY: 'minioadmin' S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' + S3_REGION: '' # optional, defaults to 'us-east-1' SPECKLE_SERVER_URL: 'http://speckle-server:3000' diff --git a/packages/server/modules/blobstorage/objectStorage.js b/packages/server/modules/blobstorage/objectStorage.js index db681a589..7b1be8c73 100644 --- a/packages/server/modules/blobstorage/objectStorage.js +++ b/packages/server/modules/blobstorage/objectStorage.js @@ -27,7 +27,7 @@ const getS3Config = () => { forcePathStyle: true, // s3ForcePathStyle: true, // signatureVersion: 'v4', - region: 'us-east-1' + region: process.env.S3_REGION || 'us-east-1' } } return s3Config diff --git a/utils/1click_image_scripts/template-docker-compose.yml b/utils/1click_image_scripts/template-docker-compose.yml index c6737fc36..0e2529d22 100644 --- a/utils/1click_image_scripts/template-docker-compose.yml +++ b/utils/1click_image_scripts/template-docker-compose.yml @@ -79,6 +79,7 @@ services: S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' S3_CREATE_BUCKET: 'true' + S3_REGION: '' # optional, defaults to 'us-east-1' FILE_SIZE_LIMIT_MB: 100 @@ -116,5 +117,6 @@ services: S3_ACCESS_KEY: 'minioadmin' S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' + S3_REGION: '' # optional, defaults to 'us-east-1' SPECKLE_SERVER_URL: 'http://speckle-server:3000' diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index 64c3b758b..e0a20cb6b 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -92,6 +92,8 @@ spec: secretKeyRef: name: {{ .Values.secretName }} key: s3_secret_key + - name: S3_REGION + value: "{{ .Values.s3.region }}" - name: FILE_IMPORT_TIME_LIMIT_MIN value: {{ .Values.fileimport_service.time_limit_min | quote }} diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index 59b585251..c9449eab6 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -141,6 +141,8 @@ spec: key: s3_secret_key - name: S3_CREATE_BUCKET value: "{{ .Values.s3.create_bucket }}" + - name: S3_REGION + value: "{{ .Values.s3.region }}" {{- end }} diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 6597de9c9..5d89ceb83 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -27,6 +27,7 @@ s3: bucket: '' access_key: '' create_bucket: 'false' + region: '' # optional, defaults to 'us-east-1' # secret_key: secret -> s3_secret_key networkPolicy: # if network policy is enabled for any service, this provides the networkPolicy with the necessary details to allow egress connections to the s3 compatible storage externalToCluster: # use if the s3 compatible storage is external to the kubernetes cluster From 5972e6b42a7dffd0ce08aada001fff5b5f1bfaea Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 14:13:44 +0100 Subject: [PATCH 07/14] fix(frontend): frontend currently cannot run as non-root (#928) Nginx needs to bind to port 80 which requires root permissions --- .../speckle-server/templates/frontend/deployment.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/utils/helm/speckle-server/templates/frontend/deployment.yml b/utils/helm/speckle-server/templates/frontend/deployment.yml index 5138d14f6..0a9ab7654 100644 --- a/utils/helm/speckle-server/templates/frontend/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend/deployment.yml @@ -43,8 +43,7 @@ spec: - NET_BIND_SERVICE privileged: false readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 20000 + runAsNonRoot: false # Allow for k8s to remove the pod from the service endpoints to stop receive traffic lifecycle: @@ -58,7 +57,7 @@ spec: port: www initialDelaySeconds: 5 periodSeconds: 5 - + env: - name: FILE_SIZE_LIMIT_MB value: {{ .Values.file_size_limit_mb | quote }} @@ -78,11 +77,7 @@ spec: priorityClassName: high-priority securityContext: - runAsNonRoot: true - runAsUser: 20000 - fsGroup: 25000 - fsGroupChangePolicy: OnRootMismatch - runAsGroup: 30000 + runAsNonRoot: false seccompProfile: type: RuntimeDefault From 19b59fa4d850245a78f3bff051cf7fe5360841ee Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 14:56:26 +0100 Subject: [PATCH 08/14] fix(frontend): frontend revert security context to prior permissions (#929) Frontend could not chown within a rw emptyDir --- .../templates/frontend/deployment.yml | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/utils/helm/speckle-server/templates/frontend/deployment.yml b/utils/helm/speckle-server/templates/frontend/deployment.yml index 0a9ab7654..7b521d0ed 100644 --- a/utils/helm/speckle-server/templates/frontend/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend/deployment.yml @@ -34,17 +34,6 @@ spec: cpu: {{ .Values.frontend.limits.cpu }} memory: {{ .Values.frontend.limits.memory }} - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - add: - - NET_BIND_SERVICE - privileged: false - readOnlyRootFilesystem: true - runAsNonRoot: false - # Allow for k8s to remove the pod from the service endpoints to stop receive traffic lifecycle: preStop: @@ -62,33 +51,4 @@ spec: - name: FILE_SIZE_LIMIT_MB value: {{ .Values.file_size_limit_mb | quote }} - volumeMounts: - - mountPath: /var/cache/nginx - name: nginx-cache - - mountPath: /tmp/nginx - name: nginx-tmp - - mountPath: /etc/nginx/conf.d - name: nginx-confd - - mountPath: /usr/local/openresty/nginx/logs - name: openresty-logs - - mountPath: /var/run/openresty - name: openresty-tmp - priorityClassName: high-priority - - securityContext: - runAsNonRoot: false - seccompProfile: - type: RuntimeDefault - - volumes: - - name: nginx-cache - emptyDir: {} - - name: nginx-confd - emptyDir: {} - - name: nginx-tmp - emptyDir: {} - - name: openresty-logs - emptyDir: {} - - name: openresty-tmp - emptyDir: {} From da7dafe819d5d82f5a8b3395ee2e321a7f1a11bc Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 15:49:10 +0100 Subject: [PATCH 09/14] fix(fileimport service): s3 is not required by fileimport service (#924) Fileimport service retreives blobs via the server storage API, and not directly from s3. Fileimport service no longer requires information or credentials about s3. --- docker-compose-speckle.yml | 7 ------- packages/fileimport-service/package.json | 2 +- .../template-docker-compose.yml | 9 +-------- .../templates/fileimport_service/deployment.yml | 15 --------------- 4 files changed, 2 insertions(+), 31 deletions(-) diff --git a/docker-compose-speckle.yml b/docker-compose-speckle.yml index 5f1262bbe..8cbf2aa8a 100644 --- a/docker-compose-speckle.yml +++ b/docker-compose-speckle.yml @@ -73,11 +73,4 @@ services: environment: DEBUG: 'fileimport-service:*' PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle' - - S3_ENDPOINT: 'http://minio:9000' - S3_ACCESS_KEY: 'minioadmin' - S3_SECRET_KEY: 'minioadmin' - S3_BUCKET: 'speckle-server' - S3_REGION: '' # optional, defaults to 'us-east-1' - SPECKLE_SERVER_URL: 'http://speckle-server:3000' diff --git a/packages/fileimport-service/package.json b/packages/fileimport-service/package.json index 122f7716d..344638ebd 100644 --- a/packages/fileimport-service/package.json +++ b/packages/fileimport-service/package.json @@ -15,7 +15,7 @@ "node": "^16.0.0" }, "scripts": { - "dev": "cross-env S3_BUCKET=speckle-server POSTGRES_URL=postgres://speckle:speckle@localhost/speckle NODE_ENV=development SPECKLE_SERVER_URL=http://localhost:3000 nodemon ./src/daemon.js", + "dev": "cross-env POSTGRES_URL=postgres://speckle:speckle@localhost/speckle NODE_ENV=development SPECKLE_SERVER_URL=http://localhost:3000 nodemon ./src/daemon.js", "parse:ifc": "node ./ifc/import_file.js /tmp/file_to_import/file 33763848d6 2e4bfb467a main File upload: steelplates.ifc", "lint": "eslint . --ext .js,.ts" }, diff --git a/utils/1click_image_scripts/template-docker-compose.yml b/utils/1click_image_scripts/template-docker-compose.yml index 0e2529d22..26dc327b9 100644 --- a/utils/1click_image_scripts/template-docker-compose.yml +++ b/utils/1click_image_scripts/template-docker-compose.yml @@ -111,12 +111,5 @@ services: environment: DEBUG: 'fileimport-service:*' PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle' - WAIT_HOSTS: 'postgres:5432, minio:9000' - - S3_ENDPOINT: 'http://minio:9000' - S3_ACCESS_KEY: 'minioadmin' - S3_SECRET_KEY: 'minioadmin' - S3_BUCKET: 'speckle-server' - S3_REGION: '' # optional, defaults to 'us-east-1' - + WAIT_HOSTS: 'postgres:5432' SPECKLE_SERVER_URL: 'http://speckle-server:3000' diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index e0a20cb6b..744b1b07f 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -80,21 +80,6 @@ spec: value: "/postgres-certificate/ca-certificate.crt" {{- end }} - - - name: S3_ENDPOINT - value: {{ .Values.s3.endpoint }} - - name: S3_ACCESS_KEY - value: {{ .Values.s3.access_key }} - - name: S3_BUCKET - value: {{ .Values.s3.bucket }} - - name: S3_SECRET_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.secretName }} - key: s3_secret_key - - name: S3_REGION - value: "{{ .Values.s3.region }}" - - name: FILE_IMPORT_TIME_LIMIT_MIN value: {{ .Values.fileimport_service.time_limit_min | quote }} From 35e26527149e1ae8cd1bb0d7ee8c640ae67a910b Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:04:50 +0100 Subject: [PATCH 10/14] feat(helm chart): node affinities, tolerations etc. are configurable (#926) * feat(helm chart): node affinities, tolerations etc. are configurable Kubernetes operators should be able to configure Speckle to be deployed on certain nodes based on rules they provide. This commit allows affinity, nodeSelector, tolerations, and topologySpreadConstrains to be provided by the operator. fixes https://github.com/specklesystems/speckle-server/issues/861 --- .../speckle-server/templates/_helpers.tpl | 13 +++ .../fileimport_service/deployment.yml | 16 ++- .../templates/frontend/deployment.yml | 12 +++ .../templates/monitoring/deployment.yml | 12 +++ .../templates/preview_service/deployment.yml | 12 +++ .../templates/server/deployment.yml | 12 +++ .../templates/webhook_service/deployment.yml | 13 +++ utils/helm/speckle-server/values.yaml | 97 ++++++++++++++++++- 8 files changed, 184 insertions(+), 3 deletions(-) diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index 5927ac7ef..e2f2b28f3 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -228,3 +228,16 @@ Params: {{- printf "false" -}} {{- end -}} {{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "speckle.renderTpl" ( dict "value" .Values.path.to.value "context" $) }} +*/}} +{{- define "speckle.renderTpl" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index 744b1b07f..66a55136f 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -65,7 +65,7 @@ spec: env: - name: SPECKLE_SERVER_URL value: "http://speckle-server:3000" - + - name: PG_CONNECTION_STRING valueFrom: secretKeyRef: @@ -83,6 +83,19 @@ spec: - name: FILE_IMPORT_TIME_LIMIT_MIN value: {{ .Values.fileimport_service.time_limit_min | quote }} + {{- if .Values.fileimport_service.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.fileimport_service.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.fileimport_service.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.fileimport_service.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + securityContext: runAsNonRoot: true runAsUser: 20000 @@ -91,7 +104,6 @@ spec: runAsGroup: 30000 seccompProfile: type: RuntimeDefault - priorityClassName: low-priority volumes: diff --git a/utils/helm/speckle-server/templates/frontend/deployment.yml b/utils/helm/speckle-server/templates/frontend/deployment.yml index 7b521d0ed..c66993978 100644 --- a/utils/helm/speckle-server/templates/frontend/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend/deployment.yml @@ -52,3 +52,15 @@ spec: value: {{ .Values.file_size_limit_mb | quote }} priorityClassName: high-priority + {{- if .Values.frontend.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.frontend.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.frontend.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.frontend.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} diff --git a/utils/helm/speckle-server/templates/monitoring/deployment.yml b/utils/helm/speckle-server/templates/monitoring/deployment.yml index 3711838f8..1be1e8997 100644 --- a/utils/helm/speckle-server/templates/monitoring/deployment.yml +++ b/utils/helm/speckle-server/templates/monitoring/deployment.yml @@ -81,3 +81,15 @@ spec: {{- end }} terminationGracePeriodSeconds: 10 + {{- if .Values.monitoring.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.monitoring.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.monitoring.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.monitoring.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} diff --git a/utils/helm/speckle-server/templates/preview_service/deployment.yml b/utils/helm/speckle-server/templates/preview_service/deployment.yml index 2ce33c418..3289a7052 100644 --- a/utils/helm/speckle-server/templates/preview_service/deployment.yml +++ b/utils/helm/speckle-server/templates/preview_service/deployment.yml @@ -76,6 +76,18 @@ spec: value: "/postgres-certificate/ca-certificate.crt" {{- end }} + {{- if .Values.preview_service.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.preview_service.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.preview_service.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.preview_service.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} priorityClassName: low-priority securityContext: diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index c9449eab6..1a4372525 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -255,6 +255,18 @@ spec: name: {{ .Values.secretName }} key: apollo_key {{- end }} + {{- if .Values.server.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.server.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.server.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.server.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.server.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.server.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.server.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.server.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} priorityClassName: high-priority securityContext: diff --git a/utils/helm/speckle-server/templates/webhook_service/deployment.yml b/utils/helm/speckle-server/templates/webhook_service/deployment.yml index 9c090d879..5c8b71cd4 100644 --- a/utils/helm/speckle-server/templates/webhook_service/deployment.yml +++ b/utils/helm/speckle-server/templates/webhook_service/deployment.yml @@ -76,6 +76,19 @@ spec: value: "/postgres-certificate/ca-certificate.crt" {{- end }} + {{- if .Values.webhook_service.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.webhook_service.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.webhook_service.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.webhook_service.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + priorityClassName: low-priority securityContext: diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 5d89ceb83..876ed33fb 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -94,9 +94,24 @@ server: sentry_dns: '' disable_tracking: false disable_tracing: false - networkPolicy: enabled: false + ## @param server.affinity Affinity for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param server.tolerations Tolerations for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] frontend: replicas: 1 @@ -108,6 +123,22 @@ frontend: memory: 512Mi networkPolicy: enabled: false + ## @param server.affinity Affinity for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param server.tolerations Tolerations for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] preview_service: replicas: 1 @@ -119,6 +150,22 @@ preview_service: memory: 4Gi networkPolicy: enabled: false + ## @param server.affinity Affinity for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param server.tolerations Tolerations for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] webhook_service: replicas: 1 @@ -130,6 +177,22 @@ webhook_service: memory: 512Mi networkPolicy: enabled: false + ## @param server.affinity Affinity for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param server.tolerations Tolerations for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] fileimport_service: replicas: 1 @@ -142,6 +205,22 @@ fileimport_service: time_limit_min: 10 networkPolicy: enabled: false + ## @param server.affinity Affinity for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param server.tolerations Tolerations for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] monitoring: replicas: 1 @@ -153,6 +232,22 @@ monitoring: memory: 512Mi networkPolicy: enabled: false + ## @param server.affinity Affinity for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param server.tolerations Tolerations for Speckle server pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] test: requests: From ca1a612a29873e66280ee079399c98a607d1d349 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:24:34 +0100 Subject: [PATCH 11/14] feat(helm chart): serviceAccounts are provided for each service (#922) ServiceAccounts for each service do not mount service account token (which allows access to the kubernetes API), and limit the secrets each user of the service account has access to. Fixes https://github.com/specklesystems/speckle-server/issues/859 --- .../templates/fileimport_service/deployment.yml | 9 +++++---- .../fileimport_service/serviceaccount.yml | 14 ++++++++++++++ .../templates/frontend/deployment.yml | 3 +++ .../templates/frontend/serviceaccount.yml | 13 +++++++++++++ .../templates/monitoring/deployment.yml | 6 ++++-- .../templates/monitoring/serviceaccount.yml | 14 ++++++++++++++ .../templates/preview_service/deployment.yml | 3 +++ .../templates/preview_service/serviceaccount.yml | 14 ++++++++++++++ .../speckle-server/templates/server/deployment.yml | 3 +++ .../templates/server/serviceaccount.yml | 14 ++++++++++++++ .../speckle-server/templates/test/deployment.yml | 3 +++ .../templates/test/serviceaccount.yml | 13 +++++++++++++ .../templates/webhook_service/deployment.yml | 3 +++ .../templates/webhook_service/serviceaccount.yml | 14 ++++++++++++++ utils/helm/speckle-server/values.yaml | 14 ++++++++++++++ 15 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 utils/helm/speckle-server/templates/fileimport_service/serviceaccount.yml create mode 100644 utils/helm/speckle-server/templates/frontend/serviceaccount.yml create mode 100644 utils/helm/speckle-server/templates/monitoring/serviceaccount.yml create mode 100644 utils/helm/speckle-server/templates/preview_service/serviceaccount.yml create mode 100644 utils/helm/speckle-server/templates/server/serviceaccount.yml create mode 100644 utils/helm/speckle-server/templates/test/serviceaccount.yml create mode 100644 utils/helm/speckle-server/templates/webhook_service/serviceaccount.yml diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index 66a55136f..aac7b446a 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -105,7 +105,11 @@ spec: seccompProfile: type: RuntimeDefault priorityClassName: low-priority - + {{- if .Values.fileimport_service.serviceAccount.create }} + serviceAccountName: {{ include "fileimport_service.name" $ }} + {{- end }} + # Should be > File import timeout to allow finishing up imports + terminationGracePeriodSeconds: 610 volumes: - name: tmp emptyDir: {} @@ -114,7 +118,4 @@ spec: configMap: name: postgres-certificate {{- end }} - - # Should be > File import timeout to allow finishing up imports - terminationGracePeriodSeconds: 610 {{- end }} diff --git a/utils/helm/speckle-server/templates/fileimport_service/serviceaccount.yml b/utils/helm/speckle-server/templates/fileimport_service/serviceaccount.yml new file mode 100644 index 000000000..cb2b01e34 --- /dev/null +++ b/utils/helm/speckle-server/templates/fileimport_service/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.fileimport_service.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "fileimport_service.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "fileimport_service.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/frontend/deployment.yml b/utils/helm/speckle-server/templates/frontend/deployment.yml index c66993978..3c5d9096b 100644 --- a/utils/helm/speckle-server/templates/frontend/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend/deployment.yml @@ -64,3 +64,6 @@ spec: {{- if .Values.frontend.topologySpreadConstraints }} topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.topologySpreadConstraints "context" $) | nindent 8 }} {{- end }} + {{- if .Values.frontend.serviceAccount.create }} + serviceAccountName: {{ include "frontend.name" $ }} + {{- end }} diff --git a/utils/helm/speckle-server/templates/frontend/serviceaccount.yml b/utils/helm/speckle-server/templates/frontend/serviceaccount.yml new file mode 100644 index 000000000..8a2877409 --- /dev/null +++ b/utils/helm/speckle-server/templates/frontend/serviceaccount.yml @@ -0,0 +1,13 @@ +{{- if .Values.frontend.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "frontend.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "frontend.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: [] # no access to any secret +{{- end -}} diff --git a/utils/helm/speckle-server/templates/monitoring/deployment.yml b/utils/helm/speckle-server/templates/monitoring/deployment.yml index 1be1e8997..dc4c7acf3 100644 --- a/utils/helm/speckle-server/templates/monitoring/deployment.yml +++ b/utils/helm/speckle-server/templates/monitoring/deployment.yml @@ -63,7 +63,10 @@ spec: {{- end }} priorityClassName: low-priority - + {{- if .Values.monitoring.serviceAccount.create }} + serviceAccountName: {{ include "monitoring.name" $ }} + {{- end }} + terminationGracePeriodSeconds: 10 securityContext: runAsNonRoot: true runAsUser: 20000 @@ -80,7 +83,6 @@ spec: name: postgres-certificate {{- end }} - terminationGracePeriodSeconds: 10 {{- if .Values.monitoring.affinity }} affinity: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.affinity "context" $) | nindent 8 }} {{- end }} diff --git a/utils/helm/speckle-server/templates/monitoring/serviceaccount.yml b/utils/helm/speckle-server/templates/monitoring/serviceaccount.yml new file mode 100644 index 000000000..687d2f61c --- /dev/null +++ b/utils/helm/speckle-server/templates/monitoring/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.monitoring.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "monitoring.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "monitoring.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/preview_service/deployment.yml b/utils/helm/speckle-server/templates/preview_service/deployment.yml index 3289a7052..ad98d3eed 100644 --- a/utils/helm/speckle-server/templates/preview_service/deployment.yml +++ b/utils/helm/speckle-server/templates/preview_service/deployment.yml @@ -89,6 +89,9 @@ spec: topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.topologySpreadConstraints "context" $) | nindent 8 }} {{- end }} priorityClassName: low-priority + {{- if .Values.preview_service.serviceAccount.create }} + serviceAccountName: {{ include "preview_service.name" $ }} + {{- end }} securityContext: runAsNonRoot: true diff --git a/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml b/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml new file mode 100644 index 000000000..bc6a25c65 --- /dev/null +++ b/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.preview_service.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "preview_service.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "preview_service.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index 1a4372525..606a90794 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -278,6 +278,9 @@ spec: seccompProfile: type: RuntimeDefault + {{- if .Values.server.serviceAccount.create }} + serviceAccountName: {{ include "server.name" $ }} + {{- end }} terminationGracePeriodSeconds: 310 volumes: - name: tmp diff --git a/utils/helm/speckle-server/templates/server/serviceaccount.yml b/utils/helm/speckle-server/templates/server/serviceaccount.yml new file mode 100644 index 000000000..2169d143b --- /dev/null +++ b/utils/helm/speckle-server/templates/server/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.server.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "server.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "server.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/test/deployment.yml b/utils/helm/speckle-server/templates/test/deployment.yml index ff72ec75a..e29205717 100644 --- a/utils/helm/speckle-server/templates/test/deployment.yml +++ b/utils/helm/speckle-server/templates/test/deployment.yml @@ -43,4 +43,7 @@ spec: seccompProfile: type: RuntimeDefault + {{- if .Values.test.serviceAccount.create }} + serviceAccountName: {{ include "test.name" $ }} + {{- end }} {{- end }} diff --git a/utils/helm/speckle-server/templates/test/serviceaccount.yml b/utils/helm/speckle-server/templates/test/serviceaccount.yml new file mode 100644 index 000000000..4c2febf10 --- /dev/null +++ b/utils/helm/speckle-server/templates/test/serviceaccount.yml @@ -0,0 +1,13 @@ +{{- if .Values.test.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "test.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "test.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: [] # does not have access to any secrets +{{- end -}} diff --git a/utils/helm/speckle-server/templates/webhook_service/deployment.yml b/utils/helm/speckle-server/templates/webhook_service/deployment.yml index 5c8b71cd4..f669e87f8 100644 --- a/utils/helm/speckle-server/templates/webhook_service/deployment.yml +++ b/utils/helm/speckle-server/templates/webhook_service/deployment.yml @@ -90,6 +90,9 @@ spec: {{- end }} priorityClassName: low-priority + {{- if .Values.webhook_service.serviceAccount.create }} + serviceAccountName: {{ include "webhook_service.name" $ }} + {{- end }} securityContext: runAsNonRoot: true diff --git a/utils/helm/speckle-server/templates/webhook_service/serviceaccount.yml b/utils/helm/speckle-server/templates/webhook_service/serviceaccount.yml new file mode 100644 index 000000000..dbdce36ba --- /dev/null +++ b/utils/helm/speckle-server/templates/webhook_service/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.webhook_service.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "webhook_service.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "webhook_service.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 876ed33fb..08e2f596a 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -83,6 +83,8 @@ server: limits: cpu: 1000m memory: 3Gi + serviceAccount: + create: true monitoring: apollo: @@ -139,6 +141,8 @@ frontend: ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] + serviceAccount: + create: true preview_service: replicas: 1 @@ -166,6 +170,8 @@ preview_service: ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] + serviceAccount: + create: true webhook_service: replicas: 1 @@ -193,6 +199,8 @@ webhook_service: ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] + serviceAccount: + create: true fileimport_service: replicas: 1 @@ -202,6 +210,8 @@ fileimport_service: limits: cpu: 1000m memory: 2Gi + serviceAccount: + create: true time_limit_min: 10 networkPolicy: enabled: false @@ -248,6 +258,8 @@ monitoring: ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] + serviceAccount: + create: true test: requests: @@ -258,6 +270,8 @@ test: memory: 512Mi networkPolicy: enabled: false + serviceAccount: + create: true secretName: server-vars From 43596e45091ec63358da45553d6008a79666a926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= <57442769+gjedlicska@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:45:13 +0200 Subject: [PATCH 12/14] refactor(server authz): refactor authz module to TypeScript (#907) * refactor(server authz): refactor authz module to TypeScript * improved roles types * Update packages/server/modules/shared/errors/base.ts Co-authored-by: Kristaps Fabians Geikins * refactor(server authz): fix PR comments Co-authored-by: Fabians --- .../{mainConstants.js => mainConstants.ts} | 17 +- packages/server/modules/shared/authz.js | 208 ------------ packages/server/modules/shared/authz.ts | 306 ++++++++++++++++++ .../shared/errors/{base.js => base.ts} | 14 +- .../shared/errors/{index.js => index.ts} | 33 +- packages/server/modules/shared/index.js | 15 +- .../server/modules/shared/test/authz.spec.js | 10 +- packages/server/scripts/streamObjects.js | 73 +++++ 8 files changed, 412 insertions(+), 264 deletions(-) rename packages/server/modules/core/helpers/{mainConstants.js => mainConstants.ts} (73%) delete mode 100644 packages/server/modules/shared/authz.js create mode 100644 packages/server/modules/shared/authz.ts rename packages/server/modules/shared/errors/{base.js => base.ts} (81%) rename packages/server/modules/shared/errors/{index.js => index.ts} (66%) create mode 100644 packages/server/scripts/streamObjects.js diff --git a/packages/server/modules/core/helpers/mainConstants.js b/packages/server/modules/core/helpers/mainConstants.ts similarity index 73% rename from packages/server/modules/core/helpers/mainConstants.js rename to packages/server/modules/core/helpers/mainConstants.ts index b65854675..0611c7b0b 100644 --- a/packages/server/modules/core/helpers/mainConstants.js +++ b/packages/server/modules/core/helpers/mainConstants.ts @@ -1,11 +1,11 @@ -const _ = require('lodash') +import _ from 'lodash' /** * Speckle role constants * - Stream - user roles in the context of a specific stream * - Server - user roles in the context of the entire server */ -const Roles = Object.freeze({ +export const Roles = Object.freeze({ Stream: { Owner: 'stream:owner', Contributor: 'stream:contributor', @@ -18,11 +18,14 @@ const Roles = Object.freeze({ } }) +export type ServerRoles = typeof Roles['Server'][keyof typeof Roles['Server']] +export type StreamRoles = typeof Roles['Stream'][keyof typeof Roles['Stream']] + /** * Speckle scope constants * - Scopes define what kind of access has a user approved for a specific access token */ -const Scopes = Object.freeze({ +export const Scopes = Object.freeze({ Streams: { Read: 'streams:read', Write: 'streams:write' @@ -51,10 +54,4 @@ const Scopes = Object.freeze({ * All scopes * @type {string[]} */ -const AllScopes = _.flatMap(Scopes, (v) => Object.values(v)) - -module.exports = { - Roles, - Scopes, - AllScopes -} +export const AllScopes = _.flatMap(Scopes, (v) => Object.values(v)) diff --git a/packages/server/modules/shared/authz.js b/packages/server/modules/shared/authz.js deleted file mode 100644 index b45703362..000000000 --- a/packages/server/modules/shared/authz.js +++ /dev/null @@ -1,208 +0,0 @@ -const { Roles, Scopes } = require('@/modules/core/helpers/mainConstants') -const { getStream } = require('@/modules/core/services/streams') -const { getRoles } = require('@/modules/shared') -const { - ForbiddenError: SFE, - UnauthorizedError: SUE, - ContextError, - BadRequestError -} = require('@/modules/shared/errors') - -const authFailed = (context, error = null, fatal = false) => ({ - context, - authResult: { authorized: false, error, fatal } -}) -const authSuccess = (context) => ({ - context, - authResult: { authorized: true, error: null } -}) - -const validateRole = - ({ requiredRole, rolesLookup, iddqd, roleGetter }) => - async ({ context, authResult }) => { - const roles = await rolesLookup() - // having the required role doesn't rescue from authResult failure - if (authResult.error) return { context, authResult } - - // role validation has nothing to do with auth... - //this check doesn't belong here, move it out to the auth pipeline - if (!context.auth) - return authFailed(context, new SUE('Cannot validate role without auth')) - - const role = roles.find((r) => r.name === requiredRole) - const myRole = roles.find((r) => r.name === roleGetter(context)) - - if (!role) return authFailed(context, new SFE('Invalid role requirement specified')) - if (!myRole) return authFailed(context, new SFE('Your role is not valid')) - if (myRole.name === iddqd || myRole.weight >= role.weight) - return authSuccess(context) - - return authFailed(context, new SFE('You do not have the required role')) - } - -const validateServerRole = ({ requiredRole }) => - validateRole({ - requiredRole, - rolesLookup: getRoles, - iddqd: Roles.Server.Admin, - roleGetter: (context) => context.role - }) - -const validateStreamRole = ({ requiredRole }) => - validateRole({ - requiredRole, - rolesLookup: getRoles, - iddqd: Roles.Stream.Owner, - roleGetter: (context) => context.stream?.role - }) - -// this could be still useful, if the operation doesnt require a stream context -// const authorizeResolver = refactor the implementation in ../index.js - -const validateScope = - ({ requiredScope }) => - async ({ context, authResult }) => { - // having the required role doesn't rescue from authResult failure - if (authResult.error) return { context, authResult } - if (!context.scopes) - return authFailed(context, new SFE('You do not have the required privileges.')) - if ( - context.scopes.indexOf(requiredScope) === -1 && - context.scopes.indexOf('*') === -1 - ) - return authFailed(context, new SFE('You do not have the required privileges.')) - return authSuccess(context) - } - -// this doesn't do any checks on the scopes, its sole responsibility is to add the -// stream object to the pipeline context -const contextRequiresStream = - (streamGetter) => - // stream getter is an async func over { streamId, userId } returning a stream object - // IoC baby... - async ({ context, authResult, params }) => { - if (!params?.streamId) - return authFailed( - context, - new ContextError("The context doesn't have a streamId") - ) - // because we're assigning to the context, it would raise if it would be null - // its probably?? safer than returning a new context - if (!context) - return authFailed(context, new ContextError('The context is not defined')) - - // cause stream getter could throw, its not a safe function if we want to - // keep the pipeline rolling - try { - const stream = await streamGetter({ - streamId: params.streamId, - userId: context?.userId - }) - if (!stream) - return authFailed( - context, - new BadRequestError('Stream inputs are malformed'), - true - ) - context.stream = stream - return { context, authResult } - } catch (err) { - // this prob needs some more detailing to not leak internal errors - return authFailed(context, new ContextError(err.message)) - } - } - -const allowForRegisteredUsersOnPublicStreamsEvenWithoutRole = async ({ - context, - authResult -}) => - context.auth && context.stream?.isPublic - ? authSuccess(context) - : { context, authResult } - -const allowForAllRegisteredUsersOnPublicStreamsWithPublicComments = async ({ - context, - authResult -}) => - context.auth && context.stream?.isPublic && context.stream?.allowPublicComments - ? authSuccess(context) - : { context, authResult } - -const allowAnonymousUsersOnPublicStreams = async ({ context, authResult }) => { - return context.stream?.isPublic ? authSuccess(context) : { context, authResult } -} - -const authPipelineCreator = (steps) => { - const pipeline = async ({ context, params }) => { - let authResult = { authorized: false, error: null } - for (const step of steps) { - ;({ context, authResult } = await step({ context, authResult, params })) - if (authResult.fatal) break - } - // validate auth result a bit... - if (authResult.authorized && authResult.error) throw new Error('Auth failure') - return { context, authResult } - } - return pipeline -} - -//we could even add an auth middleware creator -// todo move this to a webserver related module, it has no place here -const authMiddlewareCreator = (steps) => { - const pipeline = authPipelineCreator(steps) - - const middleware = async (req, res, next) => { - const { authResult } = await pipeline({ context: req.context, params: req.params }) - if (!authResult.authorized) { - let message = 'Unknown AuthZ error' - let status = 500 - if (authResult.error) { - message = authResult.error.message - if (authResult.error instanceof SUE) status = 401 - if (authResult.error instanceof SFE) status = 403 - } - - return res.status(status).json({ error: message }) - } - next() - } - return middleware -} - -// eslint-disable-next-line no-unused-vars -const exampleMiddleware = authMiddlewareCreator([ - // at some point add the context preparation here too - validateServerRole({ requiredRole: Roles.Server.User }), - validateScope({ requiredScope: Scopes.Streams.Write }), - contextRequiresStream(getStream), - validateStreamRole({ requiredRole: Roles.Stream.Reviewer }), - allowForRegisteredUsersOnPublicStreamsEvenWithoutRole -]) - -module.exports = { - authPipelineCreator, - authSuccess, - authFailed, - validateRole, - validateScope, - validateServerRole, - validateStreamRole, - contextRequiresStream, - ContextError, - authMiddlewareCreator, - allowForRegisteredUsersOnPublicStreamsEvenWithoutRole, - allowForAllRegisteredUsersOnPublicStreamsWithPublicComments, - allowAnonymousUsersOnPublicStreams, - streamWritePermissions: [ - validateServerRole({ requiredRole: Roles.Server.User }), - validateScope({ requiredScope: Scopes.Streams.Write }), - contextRequiresStream(getStream), - validateStreamRole({ requiredRole: Roles.Stream.Contributor }) - ], - streamReadPermissions: [ - validateServerRole({ requiredRole: Roles.Server.User }), - validateScope({ requiredScope: Scopes.Streams.Read }), - contextRequiresStream(getStream), - validateStreamRole({ requiredRole: Roles.Stream.Contributor }) - ] -} diff --git a/packages/server/modules/shared/authz.ts b/packages/server/modules/shared/authz.ts new file mode 100644 index 000000000..aba0aeb09 --- /dev/null +++ b/packages/server/modules/shared/authz.ts @@ -0,0 +1,306 @@ +import Express from 'express' +import { + Scopes, + Roles, + ServerRoles, + StreamRoles +} from '@/modules/core/helpers/mainConstants' +import { getRoles } from '@/modules/shared' +import { getStream } from '@/modules/core/services/streams' + +import { + BaseError, + ForbiddenError, + UnauthorizedError, + ContextError, + BadRequestError +} from '@/modules/shared/errors' +// import { getbAllRoles } from '../core/services/generic' + +interface AuthResult { + authorized: boolean +} + +interface AuthFailedResult extends AuthResult { + authorized: false + error: BaseError | null + fatal?: boolean +} + +interface Stream { + role?: StreamRoles + isPublic: boolean + allowPublicComments: boolean +} + +export interface AuthContext { + auth: boolean + userId?: string + role?: ServerRoles + token?: string + scopes?: string[] + stream?: Stream +} + +interface AuthParams { + streamId?: string +} + +interface AuthData { + context: AuthContext + authResult: AuthResult + params?: AuthParams +} + +interface AuthFailedData extends AuthData { + authResult: AuthFailedResult +} + +export const authFailed = ( + context: AuthContext, + error: BaseError | null, + fatal = false +): AuthFailedData => ({ + context, + authResult: { authorized: false, error, fatal } +}) + +export const authSuccess = (context: AuthContext): AuthData => ({ + context, + authResult: { authorized: true } +}) + +type AvailableRoles = ServerRoles | StreamRoles + +interface RoleData { + weight: number + name: T +} + +type AuthPipelineFunction = ({ + context, + authResult, + params +}: AuthData) => Promise + +const authHasFailed = (authResult: AuthResult): authResult is AuthFailedResult => + 'error' in authResult + +interface RoleValidationInput { + requiredRole: T + rolesLookup: () => Promise[]> + iddqd: T + roleGetter: (context: AuthContext) => T | null +} + +// interface StreamRoleValidationInput { +// requiredRole: StreamRoles +// rolesLookup: () => Promise +// iddqd: StreamRoles +// roleGetter: (AuthContext) => StreamRoles +// } + +export function validateRole({ + requiredRole, + rolesLookup, + iddqd, + roleGetter +}: RoleValidationInput): AuthPipelineFunction { + return async ({ context, authResult }): Promise => { + const roles = await rolesLookup() + //having the required role doesn't rescue from authResult failure + if (authHasFailed(authResult)) return { context, authResult } + + // role validation has nothing to do with auth... + //this check doesn't belong here, move it out to the auth pipeline + if (!context.auth) + return authFailed( + context, + new UnauthorizedError('Cannot validate role without auth') + ) + + const contextRole = roleGetter(context) + if (!contextRole) + return authFailed( + context, + new ForbiddenError('You do not have the required role') + ) + + const role = roles.find((r) => r.name === requiredRole) + const myRole = roles.find((r) => r.name === contextRole) + + if (!role) + return authFailed( + context, + new ForbiddenError('Invalid role requirement specified') + ) + if (!myRole) + return authFailed(context, new ForbiddenError('Your role is not valid')) + if (myRole.name === iddqd || myRole.weight >= role.weight) + return authSuccess(context) + return authFailed(context, new ForbiddenError('You do not have the required role')) + } +} + +export const validateServerRole = ({ requiredRole }: { requiredRole: ServerRoles }) => + validateRole({ + requiredRole, + rolesLookup: getRoles, + iddqd: Roles.Server.Admin, + roleGetter: (context) => context.role || null + }) + +export const validateStreamRole = ({ requiredRole }: { requiredRole: StreamRoles }) => + validateRole({ + requiredRole, + rolesLookup: getRoles, + iddqd: Roles.Stream.Owner, + roleGetter: (context) => context?.stream?.role || null + }) + +export const validateScope = + ({ requiredScope }: { requiredScope: string }): AuthPipelineFunction => + async ({ context, authResult }) => { + // having the required role doesn't rescue from authResult failure + if (authHasFailed(authResult)) return { context, authResult } + if (!context.scopes) + return authFailed( + context, + new ForbiddenError('You do not have the required privileges.') + ) + if ( + context.scopes.indexOf(requiredScope) === -1 && + context.scopes.indexOf('*') === -1 + ) + return authFailed( + context, + new ForbiddenError('You do not have the required privileges.') + ) + return authSuccess(context) + } + +type StreamGetter = (params: { streamId: string; userId?: string }) => Promise + +// this doesn't do any checks on the scopes, its sole responsibility is to add the +// stream object to the pipeline context +export const contextRequiresStream = + (streamGetter: StreamGetter): AuthPipelineFunction => + // stream getter is an async func over { streamId, userId } returning a stream object + // IoC baby... + async ({ context, authResult, params }) => { + if (!params?.streamId) + return authFailed( + context, + new ContextError("The context doesn't have a streamId") + ) + // because we're assigning to the context, it would raise if it would be null + // its probably?? safer than returning a new context + if (!context) + return authFailed(context, new ContextError('The context is not defined')) + + // cause stream getter could throw, its not a safe function if we want to + // keep the pipeline rolling + try { + const stream = await streamGetter({ + streamId: params.streamId, + userId: context?.userId + }) + if (!stream) + return authFailed( + context, + new BadRequestError('Stream inputs are malformed'), + true + ) + context.stream = stream + return { context, authResult } + } catch (err) { + // this prob needs some more detailing to not leak internal errors + const error = err as Error + return authFailed(context, new ContextError(error.message)) + } + } + +export const allowForRegisteredUsersOnPublicStreamsEvenWithoutRole: AuthPipelineFunction = + async ({ context, authResult }) => + context.auth && context.stream?.isPublic + ? authSuccess(context) + : { context, authResult } + +export const allowForAllRegisteredUsersOnPublicStreamsWithPublicComments: AuthPipelineFunction = + async ({ context, authResult }) => + context.auth && context.stream?.isPublic && context.stream?.allowPublicComments + ? authSuccess(context) + : { context, authResult } + +export const allowAnonymousUsersOnPublicStreams: AuthPipelineFunction = async ({ + context, + authResult +}) => (context.stream?.isPublic ? authSuccess(context) : { context, authResult }) + +export const authPipelineCreator = ( + steps: AuthPipelineFunction[] +): AuthPipelineFunction => { + const pipeline: AuthPipelineFunction = async ({ + context, + params, + authResult = { authorized: false } + }) => { + for (const step of steps) { + ;({ context, authResult } = await step({ context, authResult, params })) + if (authHasFailed(authResult) && authResult?.fatal) break + } + // validate auth result a bit... + if (authResult.authorized && authHasFailed(authResult)) + throw new Error('Auth failure') + return { context, authResult } + } + return pipeline +} + +interface RequestWithContext extends Express.Request { + context: AuthContext +} + +//we could even add an auth middleware creator +// todo move this to a webserver related module, it has no place here +export const authMiddlewareCreator = (steps: AuthPipelineFunction[]) => { + const pipeline = authPipelineCreator(steps) + + const middleware = async ( + req: RequestWithContext, + res: Express.Response, + next: Express.NextFunction + ) => { + const { authResult } = await pipeline({ + context: req.context as AuthContext, + params: req.params as AuthParams, + authResult: { authorized: false } + }) + if (!authResult.authorized) { + let message = 'Unknown AuthZ error' + let status = 500 + if (authHasFailed(authResult)) { + message = authResult.error?.message || message + if (authResult.error instanceof UnauthorizedError) status = 401 + if (authResult.error instanceof ForbiddenError) status = 403 + } + + return res.status(status).json({ error: message }) + } + next() + } + return middleware +} + +export const streamWritePermissions = [ + validateServerRole({ requiredRole: Roles.Server.User }), + validateScope({ requiredScope: Scopes.Streams.Write }), + contextRequiresStream(getStream as StreamGetter), + validateStreamRole({ requiredRole: Roles.Stream.Contributor }) +] +export const streamReadPermissions = [ + validateServerRole({ requiredRole: Roles.Server.User }), + validateScope({ requiredScope: Scopes.Streams.Read }), + contextRequiresStream(getStream as StreamGetter), + validateStreamRole({ requiredRole: Roles.Stream.Contributor }) +] diff --git a/packages/server/modules/shared/errors/base.js b/packages/server/modules/shared/errors/base.ts similarity index 81% rename from packages/server/modules/shared/errors/base.js rename to packages/server/modules/shared/errors/base.ts index 574ea7396..2445b64a6 100644 --- a/packages/server/modules/shared/errors/base.js +++ b/packages/server/modules/shared/errors/base.ts @@ -1,4 +1,4 @@ -const VError = require('verror') +import { VError, Options } from 'verror' /** * Base application error (don't use directly, treat it as abstract). Built on top of `verror` so that you can @@ -6,7 +6,7 @@ const VError = require('verror') * * This allows for much nicer error handling & monitoring */ -class BaseError extends VError { +export class BaseError extends VError { /** * Error code (override in child class) */ @@ -17,11 +17,7 @@ class BaseError extends VError { */ static defaultMessage = 'Unexpected error occurred!' - /** - * @param {string | null} message - * @param {import('verror').Options | Error} options - */ - constructor(message, options) { + constructor(message: string | null | undefined, options: Options | Error | undefined = undefined) { // Resolve options correctly if (options) { const cause = options instanceof Error ? options : options.cause @@ -53,8 +49,6 @@ class BaseError extends VError { * Get collected info of this object and previous errors */ info() { - return BaseError.info(this) + return BaseError.info(this as unknown as Error) } } - -module.exports = { BaseError } diff --git a/packages/server/modules/shared/errors/index.js b/packages/server/modules/shared/errors/index.ts similarity index 66% rename from packages/server/modules/shared/errors/index.js rename to packages/server/modules/shared/errors/index.ts index 957e278f6..f87a62662 100644 --- a/packages/server/modules/shared/errors/index.js +++ b/packages/server/modules/shared/errors/index.ts @@ -1,6 +1,6 @@ -const { BaseError } = require('./base') +import { BaseError } from '@/modules/shared/errors/base' -class ForbiddenError extends BaseError { +export class ForbiddenError extends BaseError { static code = 'FORBIDDEN_ERROR' static defaultMessage = 'Access to the resource is forbidden' } @@ -9,7 +9,7 @@ class ForbiddenError extends BaseError { * Use this in logic branches that should never execute, and if they do - it means * there's most definitely a bug in the code */ -class LogicError extends BaseError { +export class LogicError extends BaseError { static code = 'LOGIC_ERROR' static defaultMessage = 'An unexpected issue occurred' } @@ -17,51 +17,42 @@ class LogicError extends BaseError { /** * Use this to throw when user tries to access data that he shouldn't have access to */ -class UnauthorizedError extends BaseError { +export class UnauthorizedError extends BaseError { static code = 'UNAUTHORIZED_ACCESS_ERROR' static defaultMessage = 'Attempted unauthorized access to data' } -class NotFoundError extends BaseError { +export class NotFoundError extends BaseError { static code = 'NOT_FOUND_ERROR' static defaultMessage = "These aren't the droids you're looking for." } -class BadRequestError extends BaseError { +export class BadRequestError extends BaseError { static code = 'BAD_REQUEST_ERROR' static defaultMessage = 'The request contains invalid data' } -class ResourceMismatch extends BaseError { +export class ResourceMismatch extends BaseError { static code = 'BAD_REQUEST_ERROR' static defaultMessage = 'The target resources mismatch' } /** * Use this to validate args */ -class InvalidArgumentError extends BaseError { +export class InvalidArgumentError extends BaseError { static code = 'INVALID_ARGUMENT_ERROR' static defaultMessage = 'Invalid arguments received' } -class RichTextParseError extends BaseError { + +export class RichTextParseError extends BaseError { static code = 'RICH_TEXT_PARSE_ERROR' static defaultMessage = 'An error occurred while trying to parse the rich text document' } -class ContextError extends BaseError { +export class ContextError extends BaseError { static code = 'CONTEXT_ERROR' static defaultMessage = 'The context is missing from the request' } -module.exports = { - BadRequestError, - ForbiddenError, - UnauthorizedError, - NotFoundError, - ResourceMismatch, - InvalidArgumentError, - RichTextParseError, - ContextError, - LogicError -} +export { BaseError } diff --git a/packages/server/modules/shared/index.js b/packages/server/modules/shared/index.js index a0e949469..88cf6c808 100644 --- a/packages/server/modules/shared/index.js +++ b/packages/server/modules/shared/index.js @@ -19,21 +19,12 @@ const pubsub = new RedisPubSub({ }) /** - * @typedef {Object} AuthContextPart - * @property {boolean} auth Whether or not user is logged in - * @property {string | undefined} userId User ID, if user is logged in - * @property {string | undefined} role User role, if logged in - * @property {string | undefined} token User token, if logged in - * @property {string[] | undefined} scopes Token scopes, if logged in - */ - -/** - * @typedef {AuthContextPart & {loaders: import('@/modules/core/loaders').RequestDataLoaders}} GraphQLContext + * @typedef {import('@/modules/shared/authz').AuthContext & {loaders: import('@/modules/core/loaders').RequestDataLoaders}} GraphQLContext */ /** * Add data loaders to auth ctx - * @param {AuthContextPart} ctx + * @param {import('@/modules/shared/authz').AuthContext} ctx * @returns {GraphQLContext} */ async function addLoadersToCtx(ctx) { @@ -56,7 +47,7 @@ async function buildContext({ req, connection }) { /** * Not just Graphql server context helper: sets req.context to have an auth prop (true/false), userId and server role. - * @returns {AuthContextPart} + * @returns {import('@/modules/shared/authz').AuthContext} */ async function contextApiTokenHelper({ req, connection }) { let token = null diff --git a/packages/server/modules/shared/test/authz.spec.js b/packages/server/modules/shared/test/authz.spec.js index 0e58069e6..3f755f76a 100644 --- a/packages/server/modules/shared/test/authz.spec.js +++ b/packages/server/modules/shared/test/authz.spec.js @@ -6,7 +6,6 @@ const { validateRole, validateScope, contextRequiresStream, - ContextError, allowForAllRegisteredUsersOnPublicStreamsWithPublicComments, allowForRegisteredUsersOnPublicStreamsEvenWithoutRole } = require('@/modules/shared/authz') @@ -14,7 +13,8 @@ const { ForbiddenError: SFE, UnauthorizedError: SUE, BadRequestError, - UnauthorizedError + UnauthorizedError, + ContextError } = require('@/modules/shared/errors') describe('AuthZ @shared', () => { @@ -45,7 +45,11 @@ describe('AuthZ @shared', () => { }) it('Pipeline throws Error if authorized but has error', async () => { const borkedStep = async () => ({ - authResult: { authorized: true, error: new UnauthorizedError('Weird stuff') } + authResult: { + authorized: true, + error: new UnauthorizedError('Weird stuff'), + fatal: false + } }) const pipeline = authPipelineCreator([borkedStep]) try { diff --git a/packages/server/scripts/streamObjects.js b/packages/server/scripts/streamObjects.js new file mode 100644 index 000000000..0272b8f4e --- /dev/null +++ b/packages/server/scripts/streamObjects.js @@ -0,0 +1,73 @@ +require('../bootstrap') +const { getUserByEmail } = require('@/modules/core/services/users') +const { createPersonalAccessToken } = require('@/modules/core/services/tokens') +const { createStream } = require('@/modules/core/services/streams') + +const { createManyObjects } = require('@/test/helpers') +const { fetch } = require('undici') +const { init } = require(`@/app`) +const request = require('supertest') +const { exit } = require('yargs') + +const main = async () => { + const testStream = { + name: 'Test Stream 01', + description: 'wonderful test stream' + } + + // const userA = { + // name: 'd1', + // email: 'd.1@speckle.systems', + // password: 'wowwow8charsplease' + // } + // userA.id = await createUser(userA) + + const userA = await getUserByEmail({ + email: 'd.1@speckle.systems' + }) + userA.token = `Bearer ${await createPersonalAccessToken( + userA.id, + 'test token user A', + [ + 'streams:read', + 'streams:write', + 'users:read', + 'users:email', + 'tokens:write', + 'tokens:read', + 'profile:read', + 'profile:email' + ] + )}` + + testStream.id = await createStream({ ...testStream, ownerId: userA.id }) + + const { app } = await init() + + const numObjs = 5000 + const objBatch = createManyObjects(numObjs) + + const uploadRes = await request(app) + .post(`/objects/${testStream.id}`) + .set('Authorization', userA.token) + .set('Content-type', 'multipart/form-data') + .attach('batch1', Buffer.from(JSON.stringify(objBatch), 'utf8')) + + console.log(uploadRes.status) + const objectIds = objBatch.map((obj) => obj.id) + + const res = await fetch(`http://localhost:3000/api/getobjects/${testStream.id}`, { + method: 'POST', + headers: { + Authorization: userA.token, + 'Content-Type': 'application/json', + Accept: 'text/plain' + }, + body: JSON.stringify({ objects: JSON.stringify(objectIds) }) + }) + const data = await res.body.getReader().read() + console.log(data) + exit(0) +} + +main().then(console.log('created')).catch(console.log('failed')) From 259e8ec829515545743b13339a7a6b5dc19ecf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= Date: Tue, 16 Aug 2022 14:59:25 +0200 Subject: [PATCH 13/14] fix formatting caused by accepting changes on the github UI --- packages/server/modules/shared/errors/base.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/server/modules/shared/errors/base.ts b/packages/server/modules/shared/errors/base.ts index 2445b64a6..ea5730f1f 100644 --- a/packages/server/modules/shared/errors/base.ts +++ b/packages/server/modules/shared/errors/base.ts @@ -17,7 +17,10 @@ export class BaseError extends VError { */ static defaultMessage = 'Unexpected error occurred!' - constructor(message: string | null | undefined, options: Options | Error | undefined = undefined) { + constructor( + message: string | null | undefined, + options: Options | Error | undefined = undefined + ) { // Resolve options correctly if (options) { const cause = options instanceof Error ? options : options.cause From 49fdd818cec162233054e34598e01218374b842b Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:41:34 +0100 Subject: [PATCH 14/14] docs(helm chart): values.yaml is documented and json.schema provided (#932) * docs(helm chart): values.yaml is documented and json.schema provided Helm Chart values.yaml file is documented with inline comments. These have been used to generate a README (in the helm repo) and a values.json.schema file. fixes https://github.com/specklesystems/speckle-server/issues/887 fixes https://github.com/specklesystems/speckle-server/issues/867 --- package.json | 1 + utils/helm/.helm-readme-configuration.json | 22 + .../speckle-server/templates/_helpers.tpl | 8 - utils/helm/speckle-server/values.schema.json | 1012 +++++++++++++++++ utils/helm/speckle-server/values.yaml | 726 ++++++++++-- utils/helm/update-documentation.sh | 71 ++ 6 files changed, 1720 insertions(+), 120 deletions(-) create mode 100644 utils/helm/.helm-readme-configuration.json create mode 100644 utils/helm/speckle-server/values.schema.json create mode 100755 utils/helm/update-documentation.sh diff --git a/package.json b/package.json index 0bc20cff4..179923c85 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "build": "yarn workspaces foreach -ptv run build", "build:public": "yarn workspaces foreach -ptv --no-private run build", "lint": "eslint . --ext .js,.ts,.vue --max-warnings=0", + "helm:readme:generate": "./utils/helm/update-documentation.sh", "prettier:check": "prettier --check .", "prettier:fix": "prettier --write .", "circleci:check": "circleci config validate ./.circleci/config.yml", diff --git a/utils/helm/.helm-readme-configuration.json b/utils/helm/.helm-readme-configuration.json new file mode 100644 index 000000000..778170964 --- /dev/null +++ b/utils/helm/.helm-readme-configuration.json @@ -0,0 +1,22 @@ +{ + "comments": { + "format": "##" + }, + "tags": { + "param": "@param", + "section": "@section", + "descriptionStart": "@descriptionStart", + "descriptionEnd": "@descriptionEnd", + "skip": "@skip", + "extra": "@extra" + }, + "modifiers": { + "array": "array", + "object": "object", + "string": "string", + "nullable": "nullable" + }, + "regexp": { + "paramsSectionTitle": "Parameters" + } +} diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index e2f2b28f3..5272acc3c 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -94,14 +94,6 @@ Part-of label app.kubernetes.io/part-of: {{ include "speckle.name" . }} {{- end }} -{{/* -Connects to kube api-server to determine if Cilium CRD are present. -If they are we assume that Cilium is installed. -*/}} -{{- define "speckle.networkpolicy.ciliumIsPresent" -}} - -{{- end }} - {{/* Creates a network policy egress definition for connecting to Redis diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json new file mode 100644 index 000000000..29d3ed487 --- /dev/null +++ b/utils/helm/speckle-server/values.schema.json @@ -0,0 +1,1012 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "The name of the namespace in which Speckle will be deployed.", + "default": "speckle-test" + }, + "create_namespace": { + "type": "boolean", + "description": "Enabling this will create a new namespace into which Speckle will be deployed", + "default": false + }, + "domain": { + "type": "string", + "description": "The DNS host name at which this Speckle deployment will be reachable", + "default": "localhost" + }, + "ssl_canonical_url": { + "type": "boolean", + "description": "HTTPS protocol will be the preferred protocol for serving this Speckle deployment", + "default": true + }, + "cert_manager_issuer": { + "type": "string", + "description": "The name of the ClusterIssuer kubernetes resource that provides the SSL Certificate", + "default": "letsencrypt-staging" + }, + "ingress": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "The namespace in which the ingress controller is deployed.", + "default": "ingress-nginx" + }, + "controllerName": { + "type": "string", + "description": "The name of the Kubernetes pod in which the ingress controller is deployed.", + "default": "ingress-nginx" + } + } + }, + "docker_image_tag": { + "type": "string", + "description": "Speckle is published as a Docker Image. The version of the image which will be deployed is specified by this tag.", + "default": "v2.3.3" + }, + "imagePullPolicy": { + "type": "string", + "description": "Determines the conditions when the Docker Images for Speckle should be pulled from the Image registry.", + "default": "IfNotPresent" + }, + "secretName": { + "type": "string", + "description": "This is the name of the Kubernetes Secret resource in which secrets for Speckle are stored.", + "default": "server-vars" + }, + "file_size_limit_mb": { + "type": "number", + "description": "This maximum size of any single file (unit is Megabytes) that can be uploaded to Speckle", + "default": 100 + }, + "enable_prometheus_monitoring": { + "type": "boolean", + "description": "If enabled, Speckle deploys a Prometheus ServiceMonitor resource", + "default": false + }, + "prometheusMonitoring": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "If provided, deploys Speckle's Prometheus resources in the given namespace", + "default": "" + }, + "release": { + "type": "string", + "description": "If provided, adds the value to a `release` label on all the Prometheus resources deployed by Speckle", + "default": "" + } + } + }, + "db": { + "type": "object", + "properties": { + "useCertificate": { + "type": "boolean", + "description": "If enabled, the certificate defined in db.certificate is used to verify TLS connections to the Postgres database", + "default": false + }, + "maxConnectionsServer": { + "type": "number", + "description": "The number of connections to the Postgres database to provide in the connection pool", + "default": 4 + }, + "certificate": { + "type": "string", + "description": "The x509 public certificate for SSL connections to the Postgres database", + "default": "" + }, + "PGSSLMODE": { + "type": "string", + "description": "This defines the level of security used when connecting to the Postgres database", + "default": "require" + }, + "networkPolicy": { + "type": "object", + "properties": { + "port": { + "type": "string", + "description": "the port on the server providing the Postgres database (default: \"5432\")", + "default": "" + }, + "externalToCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Postgres database is hosted externally to the Kubernetes cluster", + "default": true + }, + "host": { + "type": "string", + "description": "The domain name at which the Postgres database is hosted.", + "default": "" + }, + "ipv4": { + "type": "string", + "description": "The IP address at which the Postgres database is hosted", + "default": "" + } + } + }, + "inCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Postgres database is hosted withing the same Kubernetes cluster in which Speckle will be deployed", + "default": false + }, + "podSelector": { + "type": "object", + "description": "The pod Selector yaml object used to uniquely select the Postgres database pods within the cluster and given namespace", + "default": {} + }, + "namespaceSelector": { + "type": "object", + "description": "The namespace selector yaml object used to uniquely select the namespace in which the Postgres database pods are deployed", + "default": {} + } + } + } + } + } + } + }, + "s3": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "description": "The URL at which the s3 compatible storage is hosted", + "default": "" + }, + "bucket": { + "type": "string", + "description": "The s3 compatible bucket in which Speckle data will be stored", + "default": "" + }, + "access_key": { + "type": "string", + "description": "The key of the access key used to authenticate with the s3 compatible storage", + "default": "" + }, + "create_bucket": { + "type": "string", + "description": "If enabled, will create a bucket with the given bucket name at this endpoint", + "default": "false" + }, + "region": { + "type": "string", + "description": "The region in which the bucket resides (or will be created in).", + "default": "" + }, + "networkPolicy": { + "type": "object", + "properties": { + "port": { + "type": "string", + "description": "the port on the server providing the s3 compatible storage (default: \"443\")", + "default": "" + }, + "externalToCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the s3 compatible storage is hosted externally to the Kubernetes cluster", + "default": true + }, + "host": { + "type": "string", + "description": "The domain name at which the s3 compatible storage is hosted.", + "default": "" + }, + "ipv4": { + "type": "string", + "description": "The IP address at which the s3 compatible storage is hosted", + "default": "" + } + } + }, + "inCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the s3 compatible storage is hosted withing the same Kubernetes cluster in which Speckle will be deployed", + "default": false + }, + "podSelector": { + "type": "object", + "description": "The pod Selector yaml object used to uniquely select the s3 compatible storage pods within the cluster and given namespace", + "default": {} + }, + "namespaceSelector": { + "type": "object", + "description": "The namespace selector yaml object used to uniquely select the namespace in which the s3 compatible storage pods are deployed", + "default": {} + } + } + } + } + } + } + }, + "redis": { + "type": "object", + "properties": { + "networkPolicy": { + "type": "object", + "properties": { + "port": { + "type": "string", + "description": "the port on the server providing the Redis store (default: \"6379\")", + "default": "" + }, + "externalToCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Redis store is hosted externally to the Kubernetes cluster", + "default": true + }, + "host": { + "type": "string", + "description": "The domain name at which the Redis store is hosted.", + "default": "" + }, + "ipv4": { + "type": "string", + "description": "The IP address at which the Redis store is hosted", + "default": "" + } + } + }, + "inCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Redis store is hosted withing the same Kubernetes cluster in which Speckle will be deployed", + "default": false + }, + "podSelector": { + "type": "object", + "description": "The pod Selector yaml object used to uniquely select the Redis store pods within the cluster and given namespace", + "default": {} + }, + "namespaceSelector": { + "type": "object", + "description": "The namespace selector yaml object used to uniquely select the namespace in which the Redis store pods are deployed", + "default": {} + } + } + } + } + } + } + }, + "server": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Server pod to be deployed within the cluster.", + "default": 1 + }, + "auth": { + "type": "object", + "properties": { + "local": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can register and authenticate with an email address and password.", + "default": true + } + } + }, + "google": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can authenticate via Google with their Google account credentials.", + "default": false + }, + "client_id": { + "type": "string", + "description": "This is the ID for Speckle that you have registered with Google.", + "default": "" + } + } + }, + "github": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can authenticate via Github with their Github account credentials.", + "default": false + }, + "client_id": { + "type": "string", + "description": "This is the ID for Speckle that you have registered with Github", + "default": "" + } + } + }, + "azure_ad": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can authenticate via Azure Active Directory.", + "default": false + }, + "org_name": { + "type": "string", + "description": "This is the Organisation Name that you have registered with Azure", + "default": "" + }, + "identity_metadata": { + "type": "string", + "description": "This is the identity metadata for Speckle that you have registered with Azure", + "default": "" + }, + "issuer": { + "type": "string", + "description": "This is the issuer name for Speckle that you have registered with Azure", + "default": "" + }, + "client_id": { + "type": "string", + "description": "This is the ID for Speckle that you have registered with Azure", + "default": "" + } + } + } + } + }, + "email": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, Speckle can send email to users - for example, email verification for account registration.", + "default": false + }, + "host": { + "type": "string", + "description": "The domain name or IP address of the server hosting the email service.", + "default": "" + }, + "port": { + "type": "string", + "description": "The port on the server for the email service.", + "default": "" + }, + "username": { + "type": "string", + "description": "The username with which Speckle will authenticate with the email service.", + "default": "" + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "500m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "1Gi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the server Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the server Pod.", + "default": "3Gi" + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + }, + "monitoring": { + "type": "object", + "properties": { + "apollo": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "(Optional) If enabled, exports metrics from the GraphQL API to Apollo Graphql Studio.", + "default": false + }, + "graph_id": { + "type": "string", + "description": "The ID for Speckle that you registered in Apollo Graphql Studio.", + "default": "" + } + } + } + } + }, + "sentry_dns": { + "type": "string", + "description": "(Optional) The Data Source Name that was provided by Sentry.io", + "default": "" + }, + "disable_tracking": { + "type": "boolean", + "description": "If set to true, will prevent tracking metrics from being collected", + "default": false + }, + "disable_tracing": { + "type": "boolean", + "description": "If set to true, will prevent tracing metrics from being collected", + "default": false + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle server pods scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle server pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle server pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle server pod scheduling", + "default": [], + "items": { + "type": "object" + } + } + } + }, + "frontend": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Frontend pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "250m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "256Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the frontend Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the frontend Pod.", + "default": "512Mi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle frontend pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle frontend pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle frontend pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle frontend pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "preview_service": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Preview Service pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "500m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "2Gi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Preview Service Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Preview Service Pod.", + "default": "4Gi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle Preview Service pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle Preview Service pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle Preview Service pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle Preview Service pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "webhook_service": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Webhook Service pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "500m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "2Gi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Webhook Service Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Webhook Service Pod.", + "default": "4Gi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle Webhook Service pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle Webhook Service pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle Webhook Service pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle Webhook Service pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "fileimport_service": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the FileImport Service pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "100m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "512Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the FileImport Service Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the FileImport Service Pod.", + "default": "2Gi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle FileImport Service pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle FileImport Service pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle FileImport Service pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle FileImport Service pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + }, + "time_limit_min": { + "type": "number", + "description": "The maximum time that a file can take to be processed by the FileImport Service.", + "default": 10 + } + } + }, + "monitoring": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Monitoring pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "100m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "64Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Monitoring Pod in a given period.", + "default": "200m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Monitoring Pod.", + "default": "512Mi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle Monitoring pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle Monitoring pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle Monitoring pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle Monitoring pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "helm_test_enabled": { + "type": "boolean", + "description": "If enabled, an additional pod is deployed which verifies some functionality of Speckle to determine if it is deployed correctly", + "default": true + }, + "test": { + "type": "object", + "properties": { + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "100m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "64Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Test Pod in a given period.", + "default": "200m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Test Pod.", + "default": "512Mi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + } + } +} diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 08e2f596a..7426cf7c7 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -1,292 +1,794 @@ +## @section Namespace +## + +## @param namespace The name of the namespace in which Speckle will be deployed. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ +## namespace: speckle-test +## @param create_namespace Enabling this will create a new namespace into which Speckle will be deployed +## The name of the namespace to create should be provided in the `namespace` parameter. +## +create_namespace: false + +## @section SSL +## + +## @param domain The DNS host name at which this Speckle deployment will be reachable +## domain: localhost + +## @param ssl_canonical_url HTTPS protocol will be the preferred protocol for serving this Speckle deployment +## ssl_canonical_url: true +## @param cert_manager_issuer The name of the ClusterIssuer kubernetes resource that provides the SSL Certificate +## +cert_manager_issuer: letsencrypt-staging + +## @section Ingress metadata for NetworkPolicy +## @descriptionStart +## This section is ignored unless networkPolicy is enabled for frontend or server. +## The NetworkPolicy uses this value to enable connections from the ingress controller pod in this namespace to reach Speckle. +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## @descriptionEnd +## +ingress: + ## @param ingress.namespace The namespace in which the ingress controller is deployed. + namespace: ingress-nginx + ## @param ingress.controllerName The name of the Kubernetes pod in which the ingress controller is deployed. + controllerName: ingress-nginx + +## @section Common parameters +## +## @param docker_image_tag Speckle is published as a Docker Image. The version of the image which will be deployed is specified by this tag. +## docker_image_tag: v2.3.3 +## @param imagePullPolicy Determines the conditions when the Docker Images for Speckle should be pulled from the Image registry. +## ref: https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy +## +imagePullPolicy: IfNotPresent + +## @param secretName This is the name of the Kubernetes Secret resource in which secrets for Speckle are stored. +## Secrets within this Secret resource may include Postgres and Redis connectin strings, S3 secret values, email server passwords, etc.. +## The expected key within the Secret resource is indicated elsewhere in this values.yaml file. +## This is expected to be an opaque Secret resource type. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## +secretName: server-vars + +## @param file_size_limit_mb This maximum size of any single file (unit is Megabytes) that can be uploaded to Speckle +## +file_size_limit_mb: 100 + +## @section Monitoring +## @descriptionStart +## This enables metrics generated by Speckle to be ingested by Prometheus: https://prometheus.io/ +## Enabling this requires Prometheus to have been deployed prior, as this resource expects the Prometheus Customer Resource Definition +## for the ServiceMonitor to already be existing within the cluster. +## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#related-resources +## @descriptionEnd +## + +## @param enable_prometheus_monitoring If enabled, Speckle deploys a Prometheus ServiceMonitor resource +## +enable_prometheus_monitoring: false + +prometheusMonitoring: + ## @param prometheusMonitoring.namespace If provided, deploys Speckle's Prometheus resources in the given namespace + ## Prometheus prior to v0.19.0, or any version when deployed with default parameters, expects ServiceMonitors to be deployed within the same namespace. + ## This parameter allows the Prometheus resources provided by Speckle to be deployed in another namespace. + ## This allows Prometheus (< v0.19.0 or any version with default configuration) to be deployed in a separate namespace from Speckle. + ## Note that Speckle expect the namespace to exist prior to deployment. + ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#related-resources + ## + namespace: '' + ## @param prometheusMonitoring.release If provided, adds the value to a `release` label on all the Prometheus resources deployed by Speckle + ## Prometheus prior to v0.19.0, or any version when deployed with default parameters, expects ServiceMonitors to be selectable on the release label. + ## This parameter allows Prometheus to be deployed with a non-default release name. + ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#related-resources + ## + release: '' + +## @section Postgres Database +## @descriptionStart +## Defines parameters related to connections to the Postgres database. +## A secret containing the connection string to the Postgres database must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the secret must match the `secretName` parameter, and the key within this secret must be `postgres_url`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## db: # postgres_url: secret -> postgres_url + ## @param db.useCertificate If enabled, the certificate defined in db.certificate is used to verify TLS connections to the Postgres database + ## useCertificate: false + ## @param db.maxConnectionsServer The number of connections to the Postgres database to provide in the connection pool + ## maxConnectionsServer: 4 + ## @param db.certificate The x509 public certificate for SSL connections to the Postgres database + ## Use of this certificate requires db.useCertificate to be enabled and an appropriate value for db.PGSSLMODE provided. + ## The value must be formatted as a multi-line string. We recommend using the pipe-symbol and taking care to + ## indent all lines of the value correctly. + ## ref: https://helm.sh/docs/chart_template_guide/yaml_techniques/#strings-in-yaml + ## certificate: '' # Multi-line string with the contents of `ca-certificate.crt` + ## @param db.PGSSLMODE This defines the level of security used when connecting to the Postgres database + ## Postgres provides different froms of protection from different types of threat when communicating between the client (Speckle) and the Postgres database. + ## ref: https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION + ## PGSSLMODE: require - networkPolicy: # if network policy is enabled for any service, this provides the networkPolicy with the necessary details to allow egress connections to the database - port: '' # the port to connect to, if known (default: "5432") - externalToCluster: # use if the database is external to the kubernetes cluster - enabled: true # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed - host: '' # Domain name, or provide IP address. If both are provided IP address takes precedence - ipv4: '' # IP address of the externally hosted Database. If not known, provide the host instead. If both are provided the IP address takes precedence. - inCluster: # use if the database is deployed within the same kubernetes cluster as Speckle - enabled: false # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed - podSelector: {} # the selector to match with pod of the deployed database instance - namespaceSelector: {} # the selector to match the namespace in which the database pod is deployed + ## @extra db.networkPolicy If networkPolicy is enabled for any service, this provides the NetworkPolicy with the necessary details to allow egress connections to the Postgres database + ## + networkPolicy: + ## @param db.networkPolicy.port the port on the server providing the Postgres database (default: "5432") + ## + port: '' + ## @extra db.networkPolicy.externalToCluster Only required if the Postgres database is not hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + externalToCluster: + ## @param db.networkPolicy.externalToCluster.enabled If enabled, indicates that the Postgres database is hosted externally to the Kubernetes cluster + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only one deployed + ## + enabled: true + ## @param db.networkPolicy.externalToCluster.host The domain name at which the Postgres database is hosted. + ## This should match the value provided within the connection string. + ## Provide the IP address if available (use the `ipv4` parameter), as the IP address takes precedence. + ## + host: '' + ## @param db.networkPolicy.externalToCluster.ipv4 The IP address at which the Postgres database is hosted + ## This should be an IP address not within the Kubernetes Cluster Pod or Service IP ranges. + ## If both host and ipv4 parameters are provided, ipv4 takes precedence and host is ignored. + ## + ipv4: '' + ## @extra db.networkPolicy.inCluster Only required if the Postgres database is hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + inCluster: + ## @param db.networkPolicy.inCluster.enabled If enabled, indicates that the Postgres database is hosted withing the same Kubernetes cluster in which Speckle will be deployed + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only set of egress network policy rules deployed. + ## + enabled: false + ## @param db.networkPolicy.inCluster.podSelector The pod Selector yaml object used to uniquely select the Postgres database pods within the cluster and given namespace + ## This is a Kubernetes podSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podSelector: {} + ## @param db.networkPolicy.inCluster.namespaceSelector The namespace selector yaml object used to uniquely select the namespace in which the Postgres database pods are deployed + ## This is a Kubernetes namespaceSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + namespaceSelector: {} +## @section S3 Compatible Storage +## @descriptionStart +## Defines parameters related to connecting to the S3 compatible storage. +## A secret containing the secret key must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `s3_secret_key`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## s3: + ## @param s3.endpoint The URL at which the s3 compatible storage is hosted + ## The url should be prefixed by the protocol (e.g. `https://`) + ## The url may need to include the port if it is not the default (e.g. `443` for `https` protocol) + ## endpoint: '' + ## @param s3.bucket The s3 compatible bucket in which Speckle data will be stored + ## The access key should be granted write permissions to this bucket + ## bucket: '' + ## @param s3.access_key The key of the access key used to authenticate with the s3 compatible storage + ## access_key: '' + ## @param s3.create_bucket If enabled, will create a bucket with the given bucket name at this endpoint + ## If enabled, the access_key must be granted the appropriate bucket creation privileges + ## create_bucket: 'false' - region: '' # optional, defaults to 'us-east-1' - # secret_key: secret -> s3_secret_key - networkPolicy: # if network policy is enabled for any service, this provides the networkPolicy with the necessary details to allow egress connections to the s3 compatible storage - externalToCluster: # use if the s3 compatible storage is external to the kubernetes cluster - enabled: true # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed - inCluster: # use if the s3 compatible storage is deployed within the same kubernetes cluster as Speckle - enabled: false # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed - podSelector: {} # the selector to match with pod of the deployed s3 compatible storage instance - namespaceSelector: {} # the selector to match the namespace in which the s3 compatible storage pod is deployed + ## @param s3.region The region in which the bucket resides (or will be created in). + ## If not provided, defaults to `us-east-1`. For many providers of s3 compatible storage, such as minio, this value may be ignored. + ## + region: '' + ## @extra s3.networkPolicy If networkPolicy is enabled for any service, this provides the NetworkPolicy with the necessary details to allow egress connections to the s3 compatible storage + ## + networkPolicy: + ## @param s3.networkPolicy.port the port on the server providing the s3 compatible storage (default: "443") + ## + port: '' + ## @extra s3.networkPolicy.externalToCluster Only required if the s3 compatible storage is not hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + externalToCluster: + ## @param s3.networkPolicy.externalToCluster.enabled If enabled, indicates that the s3 compatible storage is hosted externally to the Kubernetes cluster + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only one deployed + ## + enabled: true + ## @param s3.networkPolicy.externalToCluster.host The domain name at which the s3 compatible storage is hosted. + ## This should match the value provided within the connection string. + ## Provide the IP address if available (use the `ipv4` parameter), as the IP address takes precedence. + ## + host: '' + ## @param s3.networkPolicy.externalToCluster.ipv4 The IP address at which the s3 compatible storage is hosted + ## This should be an IP address not within the Kubernetes Cluster Pod or Service IP ranges. + ## If both host and ipv4 parameters are provided, ipv4 takes precedence and host is ignored. + ## + ipv4: '' + ## @extra s3.networkPolicy.inCluster Only required if the s3 compatible storage is hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + inCluster: + ## @param s3.networkPolicy.inCluster.enabled If enabled, indicates that the s3 compatible storage is hosted withing the same Kubernetes cluster in which Speckle will be deployed + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only set of egress network policy rules deployed. + ## + enabled: false + ## @param s3.networkPolicy.inCluster.podSelector The pod Selector yaml object used to uniquely select the s3 compatible storage pods within the cluster and given namespace + ## This is a Kubernetes podSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podSelector: {} + ## @param s3.networkPolicy.inCluster.namespaceSelector The namespace selector yaml object used to uniquely select the namespace in which the s3 compatible storage pods are deployed + ## This is a Kubernetes namespaceSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + namespaceSelector: {} +## @section Redis Store +## @descriptionStart +## Defines parameters related to connecting to the Redis Store. +## A secret containing the redis url (containing domain, username, and password) must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret resource must be `redis_url`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## redis: - # redis_url: secret -> redis_url - networkPolicy: # if network policy is enabled for any service, this provides the networkPolicy with the necessary details to allow egress connections to redis - port: '' # the port to connect to, if known (default: "6379") - externalToCluster: # use if redis is external to the kubernetes cluster - enabled: true # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed - host: '' # Domain name of the externally hosted Redis. It is preferable to provide the IP address. If both are provided IP address takes precedence - ipv4: '' # IP address of the externally hosted Redis. If not known, provide the host instead. If both are provided the IP address takes precedence. - inCluster: # use if redis is deployed within the same kubernetes cluster as Speckle - enabled: false # only one of externalToCluster or inCluster should be enabled, if both are enabled only inCluster is deployed - podSelector: {} # the selector to match with pod of the deployed Redis instance - namespaceSelector: {} # the selector to match the namespace in which the Redis pod is deployed + ## @extra redis.networkPolicy If networkPolicy is enabled for Speckle server, this provides the NetworkPolicy with the necessary details to allow egress connections to the Redis store + ## + networkPolicy: + ## @param redis.networkPolicy.port the port on the server providing the Redis store (default: "6379") + ## + port: '' + ## @extra redis.networkPolicy.externalToCluster Only required if the Redis store is not hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + externalToCluster: + ## @param redis.networkPolicy.externalToCluster.enabled If enabled, indicates that the Redis store is hosted externally to the Kubernetes cluster + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only one deployed + ## + enabled: true + ## @param redis.networkPolicy.externalToCluster.host The domain name at which the Redis store is hosted. + ## This should match the value provided within the connection string. + ## Provide the IP address if available (use the `ipv4` parameter), as the IP address takes precedence. + ## + host: '' + ## @param redis.networkPolicy.externalToCluster.ipv4 The IP address at which the Redis store is hosted + ## This should be an IP address not within the Kubernetes Cluster Pod or Service IP ranges. + ## If both host and ipv4 parameters are provided, ipv4 takes precedence and host is ignored. + ## + ipv4: '' + ## @extra redis.networkPolicy.inCluster is only required if the Redis store is hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + inCluster: + ## @param redis.networkPolicy.inCluster.enabled If enabled, indicates that the Redis store is hosted withing the same Kubernetes cluster in which Speckle will be deployed + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only set of egress network policy rules deployed. + ## + enabled: false + ## @param redis.networkPolicy.inCluster.podSelector The pod Selector yaml object used to uniquely select the Redis store pods within the cluster and given namespace + ## This is a Kubernetes podSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podSelector: {} + ## @param redis.networkPolicy.inCluster.namespaceSelector The namespace selector yaml object used to uniquely select the namespace in which the Redis store pods are deployed + ## This is a Kubernetes namespaceSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + namespaceSelector: {} +## @section Server +## @descriptionStart +## Defines parameters related to the backend server component of Speckle. +## A secret containing the an unique value (this can be generated randomly) must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Secret resource must be `session_secret`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## server: + ## @param server.replicas The number of instances of the Server pod to be deployed within the cluster. + ## replicas: 1 - # session_secret: secret -> `session_secret` + ## @extra server.auth Speckle provides a number of different mechanisms for authenticating users. Each available option must be configured here. + ## auth: local: + ## @param server.auth.local.enabled If enabled, users can register and authenticate with an email address and password. + ## The login details are stored in the Postgres database connected to Speckle and are encrypted. + ## enabled: true google: + ## @param server.auth.google.enabled If enabled, users can authenticate via Google with their Google account credentials. + ## If enabling Google, the `server.auth.google.client_id` parameter is required. + ## A secret containing the client secret must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `google_client_secret`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.auth.google.client_id This is the ID for Speckle that you have registered with Google. + ## client_id: '' - # client_secret: secret -> `google_client_secret` github: + ## @param server.auth.github.enabled If enabled, users can authenticate via Github with their Github account credentials. + ## If enabling Github authentication, the `server.auth.github.client_id` parameter is required. + ## A secret containing the client secret must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `github_client_secret`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.auth.github.client_id This is the ID for Speckle that you have registered with Github + ## client_id: '' - # client_secret: secret -> `github_client_secret` azure_ad: + ## @param server.auth.azure_ad.enabled If enabled, users can authenticate via Azure Active Directory. + ## If enabling Azure Active Directory authentication, a secret containing the client secret must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `azure_ad_client_secret`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.auth.azure_ad.org_name This is the Organisation Name that you have registered with Azure + ## org_name: '' + ## @param server.auth.azure_ad.identity_metadata This is the identity metadata for Speckle that you have registered with Azure + ## identity_metadata: '' + ## @param server.auth.azure_ad.issuer This is the issuer name for Speckle that you have registered with Azure + ## issuer: '' + ## @param server.auth.azure_ad.client_id This is the ID for Speckle that you have registered with Azure + ## client_id: '' - # client_secret: secret -> `azure_ad_client_secret` + ## @extra server.email Speckle can communicate with users via email, providing account verification and notification. + ## email: + ## @param server.email.enabled If enabled, Speckle can send email to users - for example, email verification for account registration. + ## If enabling Email, a secret containing the password to the email server must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `email_password`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.email.host The domain name or IP address of the server hosting the email service. + ## host: '' + ## @param server.email.port The port on the server for the email service. + ## port: '' + ## @param server.email.username The username with which Speckle will authenticate with the email service. + ## Note that the `email_password` is expected to be provided in the Kubernetes Secret with the name provided in the `secretName` parameter. + ## username: '' - # password: secret -> `email_password` requests: + ## @param server.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 500m + ## @param server.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 1Gi limits: + ## @param server.limits.cpu The maximum CPU that will be made available to the server Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 1000m + ## @param server.limits.memory The maximum Memory that will be made available to the server Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 3Gi serviceAccount: + ## @param server.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## create: true monitoring: apollo: + ## @param server.monitoring.apollo.enabled (Optional) If enabled, exports metrics from the GraphQL API to Apollo Graphql Studio. + ## If enabling Apollo, a secret containing the key to the Apollo Graphql Studio API must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `apollo_key`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.monitoring.apollo.graph_id The ID for Speckle that you registered in Apollo Graphql Studio. + ## graph_id: '' - # key: secret -> `apollo_key` - # Sentry specific: + ## @param server.sentry_dns (Optional) The Data Source Name that was provided by Sentry.io + ## Sentry.io allows events within Speckle to be monitored + ## sentry_dns: '' + ## @param server.disable_tracking If set to true, will prevent tracking metrics from being collected + ## Setting this value to false requires `sentry_dns` to be set + ## disable_tracking: false + ## @param server.disable_tracing If set to true, will prevent tracing metrics from being collected + ## Setting this value to false requires `sentry_dns` to be set + ## disable_tracing: false networkPolicy: + ## @param server.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `ingress`, `postgres.networkPolicy`, `redis.networkPolicy`, and `s3.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## enabled: false - ## @param server.affinity Affinity for Speckle server pods assignment + ## @param server.affinity Affinity for Speckle server pods scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity ## affinity: {} - ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## @param server.nodeSelector Node labels for Speckle server pods scheduling ## ref: https://kubernetes.io/docs/user-guide/node-selection/ ## nodeSelector: {} - ## @param server.tolerations Tolerations for Speckle server pods assignment + ## @param server.tolerations Tolerations for Speckle server pods scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ ## tolerations: [] - ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod scheduling ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] +## @section Frontend +## @descriptionStart +## Defines parameters related to the frontend server component of Speckle. +## @descriptionEnd +## frontend: + ## @param frontend.replicas The number of instances of the Frontend pod to be deployed within the cluster. + ## replicas: 1 requests: + ## @param frontend.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 250m + ## @param frontend.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 256Mi limits: + ## @param frontend.limits.cpu The maximum CPU that will be made available to the frontend Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 1000m + ## @param frontend.limits.memory The maximum Memory that will be made available to the frontend Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 512Mi networkPolicy: + ## @param frontend.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `ingress` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## enabled: false - ## @param server.affinity Affinity for Speckle server pods assignment + ## @param frontend.affinity Affinity for Speckle frontend pod scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity ## affinity: {} - ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## @param frontend.nodeSelector Node labels for Speckle frontend pods scheduling ## ref: https://kubernetes.io/docs/user-guide/node-selection/ ## nodeSelector: {} - ## @param server.tolerations Tolerations for Speckle server pods assignment + ## @param frontend.tolerations Tolerations for Speckle frontend pods scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ ## tolerations: [] - ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## @param frontend.topologySpreadConstraints Spread Constraints for Speckle frontend pod scheduling ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] serviceAccount: + ## @param frontend.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## create: true +## @section Preview Service +## @descriptionStart +## Defines parameters related to the Preview Service component of Speckle. +## @descriptionEnd +## preview_service: + ## @param preview_service.replicas The number of instances of the Preview Service pod to be deployed within the cluster. + ## replicas: 1 requests: + ## @param preview_service.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 500m + ## @param preview_service.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 2Gi limits: + ## @param preview_service.limits.cpu The maximum CPU that will be made available to the Preview Service Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 1000m + ## @param preview_service.limits.memory The maximum Memory that will be made available to the Preview Service Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 4Gi networkPolicy: + ## @param preview_service.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## enabled: false - ## @param server.affinity Affinity for Speckle server pods assignment + ## @param preview_service.affinity Affinity for Speckle Preview Service pod scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity ## affinity: {} - ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## @param preview_service.nodeSelector Node labels for Speckle Preview Service pods scheduling ## ref: https://kubernetes.io/docs/user-guide/node-selection/ ## nodeSelector: {} - ## @param server.tolerations Tolerations for Speckle server pods assignment + ## @param preview_service.tolerations Tolerations for Speckle Preview Service pods scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ ## tolerations: [] - ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## @param preview_service.topologySpreadConstraints Spread Constraints for Speckle Preview Service pod scheduling ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] serviceAccount: + ## @param preview_service.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## create: true +## @section Webhook Service +## @descriptionStart +## Defines parameters related to the Webhook Service component of Speckle. +## @descriptionEnd +## webhook_service: + ## @param webhook_service.replicas The number of instances of the Webhook Service pod to be deployed within the cluster. + ## replicas: 1 requests: - cpu: 100m - memory: 256Mi - limits: - cpu: 200m - memory: 512Mi - networkPolicy: - enabled: false - ## @param server.affinity Affinity for Speckle server pods assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity - ## - affinity: {} - ## @param server.nodeSelector Node labels for Speckle server pods assignment - ## ref: https://kubernetes.io/docs/user-guide/node-selection/ - ## - nodeSelector: {} - ## @param server.tolerations Tolerations for Speckle server pods assignment - ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ - ## - tolerations: [] - ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment - ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ - ## - topologySpreadConstraints: [] - serviceAccount: - create: true - -fileimport_service: - replicas: 1 - requests: - cpu: 100m - memory: 512Mi - limits: - cpu: 1000m + ## @param webhook_service.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 500m + ## @param webhook_service.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 2Gi - serviceAccount: - create: true - time_limit_min: 10 + limits: + ## @param webhook_service.limits.cpu The maximum CPU that will be made available to the Webhook Service Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 1000m + ## @param webhook_service.limits.memory The maximum Memory that will be made available to the Webhook Service Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + memory: 4Gi networkPolicy: + ## @param webhook_service.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## enabled: false - ## @param server.affinity Affinity for Speckle server pods assignment + ## @param webhook_service.affinity Affinity for Speckle Webhook Service pod scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity ## affinity: {} - ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## @param webhook_service.nodeSelector Node labels for Speckle Webhook Service pods scheduling ## ref: https://kubernetes.io/docs/user-guide/node-selection/ ## nodeSelector: {} - ## @param server.tolerations Tolerations for Speckle server pods assignment + ## @param webhook_service.tolerations Tolerations for Speckle Webhook Service pods scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ ## tolerations: [] - ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## @param webhook_service.topologySpreadConstraints Spread Constraints for Speckle Webhook Service pod scheduling ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] + serviceAccount: + ## @param webhook_service.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true -monitoring: +## @section File Import Service +## @descriptionStart +## Defines parameters related to the File Import Service component of Speckle. +## @descriptionEnd +## +fileimport_service: + ## @param fileimport_service.replicas The number of instances of the FileImport Service pod to be deployed within the cluster. + ## replicas: 1 requests: + ## @param fileimport_service.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 100m + ## @param fileimport_service.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + memory: 512Mi + limits: + ## @param fileimport_service.limits.cpu The maximum CPU that will be made available to the FileImport Service Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 1000m + ## @param fileimport_service.limits.memory The maximum Memory that will be made available to the FileImport Service Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + memory: 2Gi + networkPolicy: + ## @param fileimport_service.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + ## @param fileimport_service.affinity Affinity for Speckle FileImport Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param fileimport_service.nodeSelector Node labels for Speckle FileImport Service pods scheduling + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param fileimport_service.tolerations Tolerations for Speckle FileImport Service pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param fileimport_service.topologySpreadConstraints Spread Constraints for Speckle FileImport Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] + serviceAccount: + ## @param fileimport_service.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true + ## @param fileimport_service.time_limit_min The maximum time that a file can take to be processed by the FileImport Service. + ## Files which take longer than this value to process will be cancelled. + ## If you experience repeated issues with small files taking a long time, and increasing CPU and/or memory requests & limits does not help, + ## please reach out to Speckle at https://speckle.community/ + ## + time_limit_min: 10 + +## @section Monitoring +## @descriptionStart +## Provides Speckle with metrics related to the Postgres database. +## @descriptionEnd +## +monitoring: + ## @param monitoring.replicas The number of instances of the Monitoring pod to be deployed within the cluster. + ## + replicas: 1 + requests: + ## @param monitoring.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 100m + ## @param monitoring.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 64Mi limits: + ## @param monitoring.limits.cpu The maximum CPU that will be made available to the Monitoring Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 200m + ## @param monitoring.limits.memory The maximum Memory that will be made available to the Monitoring Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 512Mi networkPolicy: + ## @param monitoring.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## enabled: false - ## @param server.affinity Affinity for Speckle server pods assignment + ## @param monitoring.affinity Affinity for Speckle Monitoring pod scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity ## affinity: {} - ## @param server.nodeSelector Node labels for Speckle server pods assignment + ## @param monitoring.nodeSelector Node labels for Speckle Monitoring pods scheduling ## ref: https://kubernetes.io/docs/user-guide/node-selection/ ## nodeSelector: {} - ## @param server.tolerations Tolerations for Speckle server pods assignment + ## @param monitoring.tolerations Tolerations for Speckle Monitoring pods scheduling ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ ## tolerations: [] - ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod assignment + ## @param monitoring.topologySpreadConstraints Spread Constraints for Speckle Monitoring pod scheduling ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ ## topologySpreadConstraints: [] serviceAccount: + ## @param monitoring.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## create: true +## @section Testing +## @descriptionStart +## Defines parameters related to testing that the deployment of Speckle has been successful. +## @descriptionEnd +## + +## @param helm_test_enabled If enabled, an additional pod is deployed which verifies some functionality of Speckle to determine if it is deployed correctly +## +helm_test_enabled: true + test: requests: + ## @param test.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 100m + ## @param test.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 64Mi limits: + ## @param test.limits.cpu The maximum CPU that will be made available to the Test Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 200m + ## @param test.limits.memory The maximum Memory that will be made available to the Test Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 512Mi networkPolicy: + ## @param test.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## enabled: false serviceAccount: + ## @param test.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## create: true - -secretName: server-vars - -enable_prometheus_monitoring: false -prometheusMonitoring: - namespace: '' - release: '' -cert_manager_issuer: letsencrypt-staging - -helm_test_enabled: true - -create_namespace: false -file_size_limit_mb: 100 -imagePullPolicy: IfNotPresent - -ingress: - namespace: ingress-nginx - controllerName: ingress-nginx diff --git a/utils/helm/update-documentation.sh b/utils/helm/update-documentation.sh new file mode 100755 index 000000000..f611fcd17 --- /dev/null +++ b/utils/helm/update-documentation.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail +if ! command -v node &> /dev/null +then + echo "πŸ›‘ node could not be found. Please install node (and ensure it is in your PATH) before trying again." + exit 1 +fi + +if ! command -v git &> /dev/null +then + echo "πŸ›‘ git could not be found. Please install git (and ensure it is in your PATH) before trying again." + exit 1 +fi + +GIT_ROOT="$(git rev-parse --show-toplevel)" + +README_GENERATOR_DIR="${GIT_ROOT}/../readme-generator-for-helm" +HELM_DIR="${GIT_ROOT}/../speckle-helm" +HELM_GIT_TARGET_BRANCH="gh-pages" +HELM_GIT_PR_BRANCH="${HELM_GIT_TARGET_BRANCH}-$(openssl rand -hex 6)" + +JSON_SCHEMA_PATH="${GIT_ROOT}/utils/helm/speckle-server/values.schema.json" + +if [ ! -d "${README_GENERATOR_DIR}" ]; then + echo "πŸ”­ Could not find readme-generator-for-helm in a sibling directory to speckle-server" + echo "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘§ Proceeding with cloning readme-generator-for-helm to a sibling directory, readme-generator-for-helm" + git clone git@github.com:bitnami-labs/readme-generator-for-helm.git "${README_GENERATOR_DIR}" +fi + +pushd "${README_GENERATOR_DIR}" + echo "✨ Updating to the latest version of readme-generator-for-helm" + git switch main + git pull origin main +popd + +if [ ! -d "${HELM_DIR}" ]; then + echo "πŸ”­ Could not find Speckle Helm in a sibling directory (named speckle-helm) to speckle-server" + echo "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘§ Proceeding with cloning Speckle's helm repository to a sibling directory, speckle-helm" + git clone git@github.com:specklesystems/helm.git "${HELM_DIR}" +fi + +pushd "${HELM_DIR}" + echo "✨ Updating to the latest version of Speckle helm" + git switch main + git pull origin main + echo "🍽 Preparing forked branch for updates" + git switch "${HELM_GIT_TARGET_BRANCH}" + git pull origin "${HELM_GIT_TARGET_BRANCH}" + git switch -c "${HELM_GIT_PR_BRANCH}" +popd + +pushd "${GIT_ROOT}" + echo "πŸ— Generating the documentation" + node "${README_GENERATOR_DIR}/bin/index.js" \ + --config "${GIT_ROOT}/utils/helm/.helm-readme-configuration.json" \ + --values "${GIT_ROOT}/utils/helm/speckle-server/values.yaml" \ + --readme "${HELM_DIR}/README.md" \ + --schema "${JSON_SCHEMA_PATH}" + + echo "πŸ› Workaround for bug in generator for schema.json: https://github.com/bitnami-labs/readme-generator-for-helm/issues/34" + TMP_OUTPUT="$(mktemp -t speckle-server-json-schema)" + jq --arg replacement 'object' '(.. | .items? | select(.type == "")).type |= $replacement' "${JSON_SCHEMA_PATH}" > "${TMP_OUTPUT}" && mv "${TMP_OUTPUT}" "${JSON_SCHEMA_PATH}" +popd + +pushd "${HELM_DIR}" + echo "🌳 Preparing Pull Request for Helm README..." + git add README.md + git commit -m "Updating README with revised parameters from values.yaml" + git push --set-upstream origin "${HELM_GIT_PR_BRANCH}" + echo "πŸ™ Please create a Pull Request, ❗️selecting gh-pages as the target branch❗️: https://github.com/specklesystems/helm/pull/new/${HELM_GIT_PR_BRANCH}" +popd