Compare commits

..
Author SHA1 Message Date
Cédric VerstraetenandCopilot 711ace1f15 docs: show all continuous deployment badges
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 19:16:38 +00:00
Cédric Verstraeten 4d5d7e3c18 Merge pull request #42 from uug-ai/chore/hub-chart-0.170.1
Upgrade Hub chart to 0.170.1 with hardened API defaults
2026-11-04 18:42:49 +01:00
Cédric Verstraeten c643012993 Merge pull request #44 from uug-ai/chore/hardened-factory-release
Deploy verified hardened Factory v2.0.13
2026-11-04 18:42:40 +01:00
Cédric VerstraetenandCopilot 913706fe3f Deploy verified hardened Factory v2.0.13 with coordinated runtime settings
Pin the signed multiarchitecture digest, route unchanged external ports to HTTP 8080, enforce restricted security context and mount writable config/tmp volumes. Exercise a legacy upgrade and ConfigMap-backed settings after pod replacement on kind.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 17:16:29 +00:00
Cédric Verstraeten d41b425035 Merge pull request #43 from uug-ai/docs/deployment-diagrams-vault-hardening
Refresh deployment diagrams and harden Vault
2026-11-04 17:33:07 +01:00
Cédric VerstraetenandCopilot f5a9eaef15 ci: wait for Vault rollout readiness
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 16:16:10 +00:00
Cédric VerstraetenandCopilot 674875a754 docs: refresh deployment diagrams and harden Vault
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 15:44:28 +00:00
Cédric VerstraetenandCopilot 718fdbe302 Pin Hub chart 0.170.1 and verify hardened API runtime
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 14:53:34 +00:00
Cédric Verstraeten 05b68ca070 Merge pull request #41 from uug-ai/fix/deployment-workflow-failures
Replace default MinIO backend with persistent Garage S3
2026-11-04 12:16:39 +01:00
Cédric VerstraetenandCopilot 45d8b1a5a3 Check enabled Hub deployments rather than optional component names
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 11:06:39 +00:00
Cédric VerstraetenandCopilot a9a6ce33b5 Use AWS CLI for portable Garage S3 verification
Avoid the broken SigV4 upload behavior of Ubuntu 22.04 curl. Use isolated path-style AWS CLI configuration and explicit checksum compatibility settings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 10:55:54 +00:00
Cédric VerstraetenandCopilot 9f069cfdaa Sign Garage S3 payload hashes explicitly on older curl
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 10:53:24 +00:00
Cédric VerstraetenandCopilot 8619bb1965 Use Garage environment secrets and document S3 setup
Avoid Garage's root-owned secret-file permission incompatibility with non-root Kubernetes mounts. Validate generated secret formats and document fresh installation, Vault provider configuration and preservation of existing MinIO data.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 10:51:01 +00:00
Cédric VerstraetenandCopilot 7bccddb995 Use persistent Garage S3 in local deployments and CI
Replace default MinIO operator installation with pinned non-root Garage, generated persistent credentials and authenticated S3 tests. Keep MinIO manifests intact for existing installations, add failure diagnostics and optional Slack handling, and align Kustomize chart pins.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 10:42:52 +00:00
Cédric VerstraetenandCopilot 90a146a6c4 Expose deployment failure diagnostics and remove empty workflow
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 10:33:13 +00:00
Cédric VerstraetenandCopilot 9c3108b259 Align promoted frontend with hardened Hub chart 0.170.0 (#40)
Use frontend port 8080 and named NodePort routing, remove the invalid registry prefix on GHCR images, and validate both frontends with the published chart before infrastructure installation. Runtime preflight passes; full-stack CI remains blocked at MinIO before Hub, with separate missing Slack webhook failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 11:30:18 +01:00
Cédric Verstraeten da59b223a7 Merge pull request #39 from uug-ai/public-release-1793786033
A new public release - 1793786033
2026-11-04 11:07:13 +01:00
uug4ai 67be7d76e8 A new public release - 1793786033 2026-11-04 09:53:55 +00:00
Cédric Verstraeten 266d036468 feat(devcontainer): add Azure CLI feature to development container 2026-09-16 14:42:45 +00:00
cedricve a20419bf96 fix(vault): standardize MQTT variable naming from MQTTURI to MQTT_URI 2026-09-14 14:29:00 +00:00
cedricve 9a47ad3f4a feat(vault): add runtime configuration options and deployment examples for local development 2026-09-14 14:23:55 +00:00
cedricve 4704e4f0ab fix(docs): clarify Kerberos Vault integration and remove CONTINUOUS_FORWARDING variable 2026-09-10 18:31:13 +00:00
Cédric Verstraeten 739d9ec944 feat(database-import): enhance user import logic with verification and error handling 2026-09-03 12:30:45 +02:00
Cédric Verstraeten f7fef4b85c feat(database-import): enhance user import logic and add verification checks 2026-09-03 10:04:30 +00:00
Cédric Verstraeten ad11d1cb70 Update README.md 2026-08-30 21:16:29 +02:00
Cédric Verstraeten 62244cff93 Merge pull request #34 from uug-ai/public-release-1786356403
A new public release - 1786356403
2026-08-13 13:30:19 +02:00
Cédric Verstraeten c7be7901ff feat(mongodb-configmap): add MongoDB connection behavior and TLS configuration options 2026-08-13 10:18:31 +00:00
Cédric Verstraeten 6f6e95ae7a docs(README): clarify cert-manager self-checks DNS configuration 2026-08-11 14:22:20 +00:00
Cédric Verstraeten bdbb9ae538 feat(cert-manager): replace CRDs enabled flag with values file for configuration 2026-08-11 14:10:32 +00:00
Cédric Verstraeten b44775ef2a feat(ingress): add public HTTPS ingress configuration with cert-manager support 2026-08-11 14:05:37 +00:00
Cédric Verstraeten d888d7e243 feat(database-import): add DocumentDB import job and script with example data 2026-08-11 13:00:10 +00:00
Cédric Verstraeten 2150a146b9 Update .gitignore 2026-08-11 13:56:41 +02:00
Cédric Verstraeten bf164c208b Force DocumentDB replacement on VPC change
AWS cannot move a DocumentDB subnet group or cluster to a different VPC. Include the VPC ID in the subnet group name and create it before destroy, and add a replace_triggered_by lifecycle rule so the cluster is recreated when the subnet group changes instead of failing on an unsupported in-place update.

Document the behaviour and the snapshot caveat in the module README.
2026-08-11 13:08:35 +02:00
Cédric Verstraeten 687a0019d8 Update .gitignore 2026-08-10 14:38:04 +02:00
uug4ai ba4f0df8be A new public release - 1786356403 2026-08-10 10:06:44 +00:00
Cédric Verstraeten bf5eb0187e Merge remote-tracking branch 'refs/remotes/origin/main' 2026-08-06 15:45:40 +00:00
Cédric Verstraeten 8c620ccf1f feat(aws): add Terraform module for Amazon EKS and DocumentDB setup 2026-08-06 12:21:27 +00:00
Cédric Verstraeten 245b276d80 Update factory image version to v2.0.4 2026-07-30 16:18:08 +02:00
Cédric Verstraeten 442137cab2 Update kerberos-hub-values.yaml 2026-07-22 09:02:05 +02:00
Cédric Verstraeten 1751c76aa5 Merge pull request #33 from kerberos-io/feat/case-share-email-template
feat(email): add case-share white-label template + values
2026-07-22 09:00:35 +02:00
Cédric Verstraeten 67c9ae9dd5 Update kerberos-factory-deployment.yaml 2026-06-30 17:29:56 +02:00
Kilian Boute a8001b37dc feat(email): add case-share white-label template + values
Adds the Kerberos-branded share_case email template (html/txt) and wires
caseShare/caseShareTitle into the hub email template values so the case-share
invitation renders with the dedicated template instead of the recording-share
one.
2026-06-23 13:46:41 +00:00
Cédric Verstraeten 85feb1d574 Update container image source in deployment YAML 2026-06-23 09:46:46 +02:00
Cédric Verstraeten 63b6607c02 Update factory image version to v2.0.2 2026-06-23 09:17:29 +02:00
cedricve 1d5c492959 Update documentation to replace 'Kerberos Agents' with 'Agents' for consistency 2026-06-22 13:00:14 +00:00
cedricve 8f41e9c375 Refactor Factory references and update deployment configurations 2026-06-22 12:36:57 +00:00
Cédric Verstraeten 0c1520c936 Merge pull request #32 from kerberos-io/public-release-1781595581
A new public release - 1781595581
2026-06-16 09:48:22 +02:00
uug4ai 469ae03f9c A new public release - 1781595581 2026-06-16 07:39:42 +00:00
Cédric Verstraeten 258f750441 Merge pull request #30 from kerberos-io/public-release-1776688403
A new public release - 1776688403
2026-04-20 14:42:17 +02:00
uug4ai 0257f5f323 A new public release - 1776688403 2026-04-20 12:33:24 +00:00
Cédric Verstraeten cdb907a980 Rename deployment and update image reference 2026-03-09 06:58:20 +01:00
Cédric Verstraeten cc57d08499 Merge pull request #28 from kerberos-io/public-release-1772868774
A new public release - 1772868774
2026-03-07 09:34:36 +01:00
uug4ai 2c7065d439 A new public release - 1772868774 2026-03-07 07:32:55 +00:00
Cédric Verstraeten bbd59806c4 Merge pull request #27 from kerberos-io/public-release-1772208444
A new public release - 1772208444
2026-03-02 12:02:45 +01:00
uug4ai 591873e4ed A new public release - 1772208444 2026-02-27 16:07:25 +00:00
Cédric Verstraeten b01604242f Merge pull request #25 from kerberos-io/public-release-1771807234
A new public release - 1771807234
2026-02-23 01:41:27 +01:00
uug4ai a6101b4cf4 A new public release - 1771807234 2026-02-23 00:40:36 +00:00
Cédric Verstraeten 1b4c1f1b6c Delete PR description autofill workflow
Remove autofill PR description workflow using OpenAI.
2026-02-07 07:47:48 +01:00
94 changed files with 4370 additions and 11656 deletions
+1
View File
@@ -3,6 +3,7 @@
"dockerFile": "Dockerfile",
"runArgs": ["--privileged"],
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
"customizations": {
+14 -34
View File
@@ -8,6 +8,7 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
#k3d: [v5.5.2, v5.6.0, v5.7.5]
@@ -29,8 +30,6 @@ jobs:
kubectl get no
kubectl get pods -A -o wide
kubectl get sc
- name: Checkout repository
uses: actions/checkout@v2
- name: Install OpenEBS
run: |
kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml
@@ -50,16 +49,11 @@ jobs:
kubectl wait --for=condition=available deployment/prometheus-operator -n default --timeout=300s
kubectl get crd
kubectl get po -A -o wide
- name: Install Minio
id: install-minio
- name: Install Garage S3
id: install-garage
run: |
git clone --depth 1 --branch v6.0.1 https://github.com/minio/operator.git && kubectl apply -k operator/
kubectl apply -f ./base/minio/minio-tenant-base.yaml
echo "Waiting for resources to be created..."
sleep 10
kubectl wait --for=jsonpath='{.status.readyReplicas}'=1 statefulset/myminio-pool-0 -n minio-tenant --timeout=300s
kubectl get po -A -o wide
kubectl get po -A -o wide | grep myminio-pool-0-0 | awk '{print $3}' | grep -q '2/2' && echo "myminio-pool-0-0 pod is running with status 2/2" || (echo "myminio-pool-0-0 pod is not running with status 2/2" && exit 1)
bash scripts/install-garage.sh
bash scripts/check-garage.sh
- name: Install MongoDB
id: install-mongodb
run: |
@@ -90,8 +84,7 @@ jobs:
kubectl apply -f ./base/vault/mongodb-configmap.yaml -n kerberos-vault
kubectl apply -f ./base/vault/kerberos-vault-deployment.yaml -n kerberos-vault
kubectl apply -f ./base/vault/kerberos-vault-service.yaml -n kerberos-vault
sleep 10
kubectl wait --for=condition=available deployment/vault -n kerberos-vault --timeout=300s
kubectl rollout status deployment/vault -n kerberos-vault --timeout=300s
kubectl get pods -A -o wide
kubectl get pods -A -o wide | grep vault | awk '{print $3}' | grep -q '1/1' && echo "vault pod is running with status 1/1" || (echo "vault pod is not running with status 1/1" && exit 1)
- name: Install Agent
@@ -106,37 +99,24 @@ jobs:
id: install-hub
run: |
helm repo add kerberos https://charts.kerberos.io
bash scripts/check-hub-runtime.sh
kubectl create namespace kerberos-hub
helm install hub kerberos/hub --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
helm install hub kerberos/hub --version 0.170.1 --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
sleep 10
kubectl wait --for=condition=available deployment/admin -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-api -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-cleanup -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend-demo -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-monitor-device -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-reactivate-subscription -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-analysis -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-counting -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-dominantcolor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-event -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-export -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-monitor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify-test -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sequence -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sprite -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-throttler -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment --all -n kerberos-hub --timeout=300s
kubectl get pods -A -o wide
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ K3d deployment successful on ${{ matrix.os }} (${{ matrix.k3d }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "✅ K3d deployment successful on ${{ matrix.os }} (${{ matrix.k3d }}) - triggered by ${{ github.actor }}"
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ K3d deployment failed on ${{ matrix.os }} (${{ matrix.k3d }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "❌ K3d deployment failed on ${{ matrix.os }} (${{ matrix.k3d }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
+18 -33
View File
@@ -8,6 +8,7 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
#kind: [v0.21.0, v0.22.0, v0.23.0, v0.24.0, v0.25.0]
@@ -26,7 +27,11 @@ jobs:
kubectl get pods -A -o wide
kubectl get sc
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Check promoted Hub runtime
run: |
helm repo add kerberos https://charts.kerberos.io
bash scripts/check-hub-runtime.sh
- name: Install OpenEBS
run: |
kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml
@@ -46,16 +51,11 @@ jobs:
kubectl wait --for=condition=available deployment/prometheus-operator -n default --timeout=300s
kubectl get crd
kubectl get po -A -o wide
- name: Install Minio
id: install-minio
- name: Install Garage S3
id: install-garage
run: |
git clone --depth 1 --branch v6.0.1 https://github.com/minio/operator.git && kubectl apply -k operator/
kubectl apply -f ./base/minio/minio-tenant-base.yaml
echo "Waiting for resources to be created..."
sleep 10
kubectl wait --for=jsonpath='{.status.readyReplicas}'=1 statefulset/myminio-pool-0 -n minio-tenant --timeout=300s
kubectl get po -A -o wide
kubectl get po -A -o wide | grep myminio-pool-0-0 | awk '{print $3}' | grep -q '2/2' && echo "myminio-pool-0-0 pod is running with status 2/2" || (echo "myminio-pool-0-0 pod is not running with status 2/2" && exit 1)
bash scripts/install-garage.sh
bash scripts/check-garage.sh
- name: Install MongoDB
id: install-mongodb
run: |
@@ -86,8 +86,7 @@ jobs:
kubectl apply -f ./base/vault/mongodb-configmap.yaml -n kerberos-vault
kubectl apply -f ./base/vault/kerberos-vault-deployment.yaml -n kerberos-vault
kubectl apply -f ./base/vault/kerberos-vault-service.yaml -n kerberos-vault
sleep 10
kubectl wait --for=condition=available deployment/vault -n kerberos-vault --timeout=300s
kubectl rollout status deployment/vault -n kerberos-vault --timeout=300s
kubectl get pods -A -o wide
kubectl get pods -A -o wide | grep vault | awk '{print $3}' | grep -q '1/1' && echo "vault pod is running with status 1/1" || (echo "vault pod is not running with status 1/1" && exit 1)
- name: Install Agent
@@ -103,36 +102,22 @@ jobs:
run: |
helm repo add kerberos https://charts.kerberos.io
kubectl create namespace kerberos-hub
helm install hub kerberos/hub --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
helm install hub kerberos/hub --version 0.170.1 --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
sleep 10
kubectl wait --for=condition=available deployment/admin -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-api -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-cleanup -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend-demo -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-monitor-device -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-reactivate-subscription -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-analysis -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-counting -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-dominantcolor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-event -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-export -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-monitor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify-test -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sequence -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sprite -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-throttler -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment --all -n kerberos-hub --timeout=300s
kubectl get pods -A -o wide
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ Kind deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "✅ Kind deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}"
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ Kind deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "❌ Kind deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
+45 -4
View File
@@ -1,4 +1,4 @@
name: Deploy on kind using Kustmize
name: Validate Kustomize, Garage and Factory on kind
on:
workflow_dispatch:
@@ -10,6 +10,7 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
#kind: [v0.21.0, v0.22.0, v0.23.0, v0.24.0, v0.25.0]
@@ -28,16 +29,56 @@ jobs:
kubectl get pods -A -o wide
kubectl get sc
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Render base Kustomize manifests
run: kubectl kustomize base > "$RUNNER_TEMP/base.yaml"
- name: Use Kind storage provisioner
run: |
kubectl get storageclass standard -o json |
jq '{apiVersion,kind,metadata:{name:"ssd-hostpath"},provisioner,parameters,volumeBindingMode,reclaimPolicy}' |
kubectl apply -f -
- name: Install Garage and test persistent S3 access
run: |
bash scripts/install-garage.sh
bash scripts/check-garage.sh
credential_hash="$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data}' | sha256sum)"
bash scripts/install-garage.sh
test "$credential_hash" = "$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data}' | sha256sum)"
bash scripts/check-garage.sh --restart
- name: Install legacy Factory and upgrade the actual base
run: |
kubectl apply -f base/namespaces/kerberos-factory.yaml
kubectl apply -f base/factory/kerberos-factory-clusterrole.yaml
kubectl create --dry-run=client -n kerberos-factory -f base/factory/kerberos-factory-deployment.yaml -o json |
jq '
del(.spec.template.spec.securityContext, .spec.template.spec.volumes) |
.spec.template.spec.containers[0] |= (
.image = "ghcr.io/uug-ai/factory:v2.0.11" |
.ports = [{"containerPort":80,"protocol":"TCP"}] |
del(.securityContext, .volumeMounts, .startupProbe, .readinessProbe, .livenessProbe) |
.env |= map(
if .name == "K8S_PROXY" then .value = "http://localhost:80"
elif .name == "PORT" then .value = "80"
else . end))' |
kubectl apply -n kerberos-factory -f -
kubectl create --dry-run=client -n kerberos-factory -f base/factory/kerberos-factory-service.yaml -o json |
jq '.spec.ports[0].targetPort = 80' |
kubectl apply -n kerberos-factory -f -
kubectl -n kerberos-factory rollout status deployment/factory --timeout=300s
kubectl apply -k base/factory
bash scripts/check-factory.sh
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ Kustomize deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "✅ Kustomize deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}"
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ Kustomize deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "❌ Kustomize deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
+15 -33
View File
@@ -8,6 +8,7 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
microk8s: [1.32/stable]
@@ -25,7 +26,7 @@ jobs:
kubectl get no
kubectl get pods -A -o wide
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Create storage class
id: create-storage-class
run: |
@@ -39,16 +40,11 @@ jobs:
kubectl wait --for=condition=available deployment/prometheus-operator -n default --timeout=300s
kubectl get crd
kubectl get po -A -o wide
- name: Install Minio
id: install-minio
- name: Install Garage S3
id: install-garage
run: |
git clone --depth 1 --branch v6.0.1 https://github.com/minio/operator.git && kubectl apply -k operator/
kubectl apply -f ./base/minio/minio-tenant-base.yaml
echo "Waiting for resources to be created..."
sleep 10
kubectl wait --for=jsonpath='{.status.readyReplicas}'=1 statefulset/myminio-pool-0 -n minio-tenant --timeout=300s
kubectl get po -A -o wide
kubectl get po -A -o wide | grep myminio-pool-0-0 | awk '{print $3}' | grep -q '2/2' && echo "myminio-pool-0-0 pod is running with status 2/2" || (echo "myminio-pool-0-0 pod is not running with status 2/2" && exit 1)
bash scripts/install-garage.sh
bash scripts/check-garage.sh
- name: Install MongoDB
id: install-mongodb
run: |
@@ -79,8 +75,7 @@ jobs:
kubectl apply -f ./base/vault/mongodb-configmap.yaml -n kerberos-vault
kubectl apply -f ./base/vault/kerberos-vault-deployment.yaml -n kerberos-vault
kubectl apply -f ./base/vault/kerberos-vault-service.yaml -n kerberos-vault
sleep 10
kubectl wait --for=condition=available deployment/vault -n kerberos-vault --timeout=300s
kubectl rollout status deployment/vault -n kerberos-vault --timeout=300s
kubectl get pods -A -o wide
kubectl get pods -A -o wide | grep vault | awk '{print $3}' | grep -q '1/1' && echo "vault pod is running with status 1/1" || (echo "vault pod is not running with status 1/1" && exit 1)
- name: Install Agent
@@ -95,38 +90,25 @@ jobs:
id: install-hub
run: |
helm repo add kerberos https://charts.kerberos.io
bash scripts/check-hub-runtime.sh
kubectl create namespace kerberos-hub
helm install hub kerberos/hub --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
helm install hub kerberos/hub --version 0.170.1 --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
sleep 10
kubectl wait --for=condition=available deployment/admin -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-api -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-cleanup -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend-demo -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-monitor-device -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-reactivate-subscription -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-analysis -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-counting -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-dominantcolor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-event -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-export -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-monitor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify-test -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sequence -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sprite -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-throttler -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment --all -n kerberos-hub --timeout=300s
kubectl get pods -A -o wide
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ MicroK8s deployment successful on ${{ matrix.os }} (${{ matrix.microk8s }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "✅ MicroK8s deployment successful on ${{ matrix.os }} (${{ matrix.microk8s }}) - triggered by ${{ github.actor }}"
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ MicroK8s deployment failed on ${{ matrix.os }} (${{ matrix.microk8s }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
bash scripts/notify-slack.sh "❌ MicroK8s deployment failed on ${{ matrix.os }} (${{ matrix.microk8s }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
-19
View File
@@ -1,19 +0,0 @@
name: Autofill PR description
on: pull_request
jobs:
openai-pr-description:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Autofill PR description if empty using OpenAI
uses: cedricve/azureopenai-pr-description@master
with:
github_token: ${{ secrets.TOKEN }}
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
azure_openai_api_key: ${{ secrets.AZURE_OPENAI_API_KEY }}
azure_openai_endpoint: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
azure_openai_version: ${{ secrets.AZURE_OPENAI_VERSION }}
overwrite_description: true
+17 -1
View File
@@ -1 +1,17 @@
**/charts
**/charts
# Terraform generated and local files
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.*
*.tfplan
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
terraform.tfvars
*.auto.tfvars
*.auto.tfvars.json
+9 -8
View File
@@ -24,16 +24,17 @@ With the Vault installed, we can proceed to configure the various components. Cu
![Configure Vault](./assets/images/configure-vault.gif)
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Storage Provider" button to complete the process. **_(!You are advised to generate more complex access and secret key for Minio, this is just for demo purposes, do not use this in production)_**
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. Verify and add the provider using the [Garage setup guide](./base/garage/README.md). These settings apply to the default Kubernetes installation; retain the matching provider settings for an existing MinIO or Docker installation.
- Minio
- MinIO/S3-compatible provider type (Garage backend)
- Enabled: true
- Provider name: minio
- Provider name: garage
- Bucket name: mybucket
- Region: na
- Hostname: myminio-hl.minio-tenant:9000
- Access key: minio
- Secret key: minio123
- Region: garage
- Hostname: garage.garage:3900
- SSL: false
- Access key: `access-key` from Secret `garage/garage-credentials`
- Secret key: `secret-key` from the same Secret
- Navigate to the `Integrations` menu and select the (+ Add Integration) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Integration" button to complete the process. **_(!You are advised to generate more complex username and password for RabbitMQ, this is just for demo purposes, do not use this in production)_**
@@ -50,7 +51,7 @@ With the Vault installed, we can proceed to configure the various components. Cu
- Enabled: true
- Account name: myaccount
- Main provider: minio
- Main provider: garage
- Day limit: 30
- Integration: rabbitmq
- Directory: \*
+10 -10
View File
@@ -1,8 +1,8 @@
### Optimized Data Filtering for Enhanced Bandwidth Efficiency and Relevance
Once your Kerberos Agents are properly connected and all recordings are stored in the Kerberos Vault, you may encounter additional challenges such as bandwidth limitations, storage constraints, and the need to efficiently locate relevant data. To accomplish this, we can configure an integration to filter the recordings, ensuring that only the relevant ones are retained.
Once your Agents are properly connected and all recordings are stored in the Vault, you may encounter additional challenges such as bandwidth limitations, storage constraints, and the need to efficiently locate relevant data. To accomplish this, we can configure an integration to filter the recordings, ensuring that only the relevant ones are retained.
Assuming all configurations are correctly set and all Kubernetes deployments are operational, you can apply the `data-filtering-deployment.yaml` deployment. This deployment will schedule a pod that listens to the configured integration in Kerberos Vault and runs a YOLOv8 model to evaluate the recordings and match them against specified conditions.
Assuming all configurations are correctly set and all Kubernetes deployments are operational, you can apply the `data-filtering-deployment.yaml` deployment. This deployment will schedule a pod that listens to the configured integration in Vault and runs a YOLOv8 model to evaluate the recordings and match them against specified conditions.
Please note that if you do not have a GPU on the device, you will need to disable the resource limit of the nvidia/gpu. Once done the filtering will run on the CPU.
@@ -18,7 +18,7 @@ Let's deploy the data filtering pod (with or without GPU support).
kubectl apply -f data-filtering-deployment.yaml
```
Each time a recording is stored in the Kerberos Vault, the `data-filtering` pod will receive a notification and execute the specified model (YOLOv8 by default). Based on the defined conditions, the `data-filtering` pod may forward the recording to a remote Kerberos Vault, trigger alerts, or send notifications.
Each time a recording is stored in the Vault, the `data-filtering` pod will receive a notification and execute the specified model (YOLOv8 by default). Based on the defined conditions, the `data-filtering` pod may forward the recording to a remote Vault, trigger alerts, or send notifications.
Ensure that the `data-filtering` workload is actively running, receiving messages from the integration, and performing the necessary processing tasks.
@@ -27,7 +27,7 @@ kubectl get po -w -A
kubectl logs -f data...
```
You might see something like below, whereas the `data-filtering` pod is iterating over recordings and frames, looking for a person. Once it finds the person it will try to `forward` the recording to a remote Kerberos Vault.
You might see something like below, whereas the `data-filtering` pod is iterating over recordings and frames, looking for a person. Once it finds the person it will try to `forward` the recording to a remote Vault.
```bash
Persons: 0, Cars: 0, Trucks: 0
@@ -49,27 +49,27 @@ Delete media from http://vault-lb.kerberos-vault/api
8) Releasing video writer and closing video capture
```
As indicated by the logs `Something went wrong while forwarding media`, the forwarding process failed due to the absence of an integration between the two `Kerberos Vaults`. Currently, only one `Kerberos Vault` is available. To enable this feature, you will need to [install a second `Kerberos Vault` in the cloud](./README.k8s-managed.md) with access to cloud storage.
As indicated by the logs `Something went wrong while forwarding media`, the forwarding process failed due to the absence of an integration between the two `Vaults`. Currently, only one `Vault` is available. To enable this feature, you will need to [install a second `Vault` in the cloud](./README.k8s-managed.md) with access to cloud storage.
### Add forwarding integration
If you have setup a secondary Kerberos Vault in the cloud, attached cloud Object storage to it, we can continue and add an additional integration through the UI.
If you have setup a secondary Vault in the cloud, attached cloud Object storage to it, we can continue and add an additional integration through the UI.
```bash
ssh -L 8080:localhost:30080 user@server-ip -p 22
```
Navigate to the `Kerberos Vault` application in your browser, access the `Integration` section, and add a new integration. This integration will connect your local Kerberos Vault to the remote Kerberos Vault, and will tell the system to set recordings in a `forwarding state`.
Navigate to the `Vault` application in your browser, access the `Integration` section, and add a new integration. This integration will connect your local Vault to the remote Vault, and will tell the system to set recordings in a `forwarding state`.
- Add an integration
- Kerberos Vault
- Vault
- Enabled: true
- Integration name: remote-vault
- Forwarding mode: continuous
- Url: http(s)://yourvault.com/api
- Provider: The name of the remote storage provider
- Access Key: The access key of the account you have created on the remote Kerberos Vault
- Secret Access Key: The secret access key of the account you have created on the remote Kerberos Vault
- Access Key: The access key of the account you have created on the remote Vault
- Secret Access Key: The secret access key of the account you have created on the remote Vault
If the integration is functioning correctly, you should observe that recordings are initially marked in gray as "To be forwarded." After a short period, some recordings will be updated to green, indicating they have been "Forwarded by."
+58 -39
View File
@@ -10,7 +10,7 @@
Kubernetes is an open-source platform for automating the deployment, scaling, and management of containerized applications. It provides features like automated deployment, self-healing, service discovery, and storage orchestration. Kubernetes is essential for modern cloud-native application development and operations.
In this tutorial, we will guide you through the installation of the Kerberos.io edge stack, which includes the Kerberos Agent, Kerberos Vault, and the Data Filtering Service. This setup enables the storage of recordings from multiple cameras at the edge, facilitating local data processing and ensuring secure and efficient management of video streams.
In this tutorial, we will guide you through installing the edge stack, which includes Agent, Vault, and the Data Filtering Service. This setup enables the storage of recordings from multiple cameras at the edge, facilitating local data processing and ensuring secure and efficient management of video streams.
## Install Kubernetes on Ubuntu with kubeadm
@@ -112,11 +112,11 @@ Ensure that all nodes are in the Ready state and all pods are in the Running sta
## Dependencies
When installing the Kerberos.io stack, several dependencies are required for storage, such as a database (e.g., MongoDB) and a message broker (e.g., RabbitMQ) for asynchronous behavior. We will install these components before setting up the Kerberos Agents and Kerberos Vault.
When installing the ecosystem, several dependencies are required for storage, such as a database (e.g., MongoDB) and a message broker (e.g., RabbitMQ) for asynchronous behavior. We will install these components before setting up the Agents and Vault.
### Clone repository
Next, we will clone this repository to our local environment. This will allow us to execute the necessary configuration files for installing the Minio operator, MongoDB Helm chart, and other required components.
Next, clone this repository for the Garage S3 manifests, MongoDB Helm chart values, and other required components.
```bash
git clone https://github.com/kerberos-io/deployment
@@ -127,11 +127,25 @@ cd deployment
When you create a Kubernetes cluster using `kubeadm` on a bare metal machine
### Object storage: MinIO
### Object storage: Garage (default)
After creating the `ssd-hostpath` StorageClass for your provisioner, run:
```sh
bash scripts/install-garage.sh
bash scripts/check-garage.sh
```
See the [Garage guide](./base/garage/README.md) for generated credentials,
Vault provider settings, persistence and existing MinIO compatibility. Garage
uses S3 port `3900` internally and NodePort `30090`; it needs no operator.
Skip the following legacy section when using the default installation.
### Optional legacy object storage: MinIO
MinIO is a high-performance, distributed object storage system that is compatible with Amazon S3 cloud storage service. It is designed to handle large-scale data storage and retrieval, making it an ideal choice for modern cloud-native applications.
In the context of the Kerberos.io stack, MinIO will be used to store recordings from the Kerberos Agents. These recordings are crucial for surveillance and monitoring purposes, and having a reliable storage solution like MinIO ensures that the data is stored securely and can be accessed efficiently.
In the context of the ecosystem, MinIO will be used to store recordings from the Agents. These recordings are crucial for surveillance and monitoring purposes, and having a reliable storage solution like MinIO ensures that the data is stored securely and can be accessed efficiently.
```bash
git clone --depth 1 --branch v6.0.1 https://github.com/minio/operator.git && kubectl apply -k operator/
@@ -213,7 +227,7 @@ To access the application, open your browser and navigate to `localhost:8080`. U
### Database: MongoDB
When using Kerberos Vault, it will persist references to the recordings stored in your storage provider in a MongoDB database. As used before, we are using `helm` to install MongoDB in our Kubernetes cluster. Within the Kerberos Vault project we are using the latest official mongodb driver, so we support all major MongoDB versions (4.x, 5.x, 6.x, 7.x).
When using Vault, it will persist references to the recordings stored in your storage provider in a MongoDB database. As used before, we are using `helm` to install MongoDB in our Kubernetes cluster. Within the Vault project we are using the latest official mongodb driver, so we support all major MongoDB versions (4.x, 5.x, 6.x, 7.x).
Have a look into the `./mongodb-values.yaml` file, you will find plenty of configurations for the MongoDB helm chart. To change the username and password of the MongoDB instance, go ahead and [find the attribute where](https://github.com/kerberos-io/vault/blob/master/kubernetes/mongodb/values.yaml#L148) you can change the root password. Please note that we are using the official [Bitnami Mongodb helm chart](https://github.com/bitnami/charts/tree/main/bitnami/mongodb), so please use their repository for more indepth configuration.
@@ -262,9 +276,9 @@ View the RabbitMQ status and wait until it's properly running
kubectl get po -w -A
```
### Kerberos Vault
### Vault
Kerberos Vault requires a configuration to connect to the MongoDB instance. To handle this a `configmap` is defined in the `./mongodb-configmap.yaml` file. Modify the MongoDB credentials in the `./mongodb-configmap.yaml` file, and make sure they match the credentials of your MongoDB instance, as described above. There are two ways of configuring the MongoDB connection, either you provide a `MONGODB_URI` or you specify the individual variables `MONGODB_USERNAME`, `MONGODB_PASSWORD`, etc.
Vault requires a configuration to connect to the MongoDB instance. To handle this a `configmap` is defined in the `./mongodb-configmap.yaml` file. Modify the MongoDB credentials in the `./mongodb-configmap.yaml` file, and make sure they match the credentials of your MongoDB instance, as described above. There are two ways of configuring the MongoDB connection, either you provide a `MONGODB_URI` or you specify the individual variables `MONGODB_USERNAME`, `MONGODB_PASSWORD`, etc.
As mentioned above a managed MongoDB is easier to setup and manage, for example for MongoDB Atlas, you will get a MongoDB URI in the form of `"mongodb+srv://xx:xx@kerberos-hub.xxx.mongodb.net/?retryWrites=true&w=majority&appName=xxx"`. By applying this value into the `MONGODB_URI` field, you will have setup your MongoDB connection successfully.
@@ -288,7 +302,7 @@ Create the `kerberos-vault` namespace.
kubectl create namespace kerberos-vault
```
Apply the manifests, so the Kerberos Vault application is deployed and knows how to connect to the MongoDB.
Apply the manifests, so the Vault application is deployed and knows how to connect to the MongoDB.
```bash
kubectl apply -f ./mongodb-configmap.yaml -n kerberos-vault
@@ -304,28 +318,33 @@ kubectl get po -w -A
#### Access the UI
If you have opted for the `NodePort` configuration, you can access the Kerberos Vault via the `http://localhost:30080` endpoint in your browser. For server installations without a GUI, consider setting up a reverse proxy to enable browser access from your local machine. Alternatively, you may utilize a `LoadBalancer` if one is available or if you are deploying on a managed Kubernetes service.
If you have opted for the `NodePort` configuration, you can access the Vault via the `http://localhost:30080` endpoint in your browser. For server installations without a GUI, consider setting up a reverse proxy to enable browser access from your local machine. Alternatively, you may utilize a `LoadBalancer` if one is available or if you are deploying on a managed Kubernetes service.
```bash
ssh -L 8080:localhost:30080 user@server-ip -p 22
```
#### Configure the Kerberos Vault
#### Configure the Vault
With the Kerberos Vault installed, we can proceed to configure the various components. Currently, this must be done through the Kerberos Vault UI, but we plan to make it configurable via environment variables, eliminating the need for manual UI configurations.
With the Vault installed, we can proceed to configure the various components. Currently, this must be done through the Vault UI, but we plan to make it configurable via environment variables, eliminating the need for manual UI configurations.
![Configure Vault](./assets/images/configure-vault.gif)
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Storage Provider" button to complete the process.
- Minio
- MinIO/S3-compatible provider type (Garage backend)
- Enabled: true
- Provider name: minio
- Provider name: garage
- Bucket name: mybucket
- Region: na
- Hostname: myminio-hl.minio-tenant:9000
- Access key: minio
- Secret key: minio123
- Region: garage
- Hostname: garage.garage:3900
- SSL: false
- Access key: `access-key` from Secret `garage/garage-credentials`
- Secret key: `secret-key` from the same Secret
For the optional legacy MinIO installation, retain that backend's hostname,
credentials and provider name instead. See the [Garage guide](./base/garage/README.md)
before switching an existing installation.
- Navigate to the `Integrations` menu and select the (+ Add Integration) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Integration" button to complete the process.
@@ -342,35 +361,35 @@ With the Kerberos Vault installed, we can proceed to configure the various compo
- Enabled: true
- Account name: myaccount
- Main provider: minio
- Main provider: garage (or the retained provider for an existing installation)
- Day limit: 30
- Integration: rabbitmq
- Directory: \*
- Access key: XJoi2@bgSOvOYBy# (or generate new keys, but don't forget to update them in the next steps)
- Secret key: OGGqat4lXRpL@9XBYc8FUaId@5 (or generate new keys, but don't forget to update them in the next steps)
### Create a Kerberos Agent
### Create an Agent
After deploying the Kerberos Vault and configuring the necessary services for storage, database, and integration, you can proceed to deploy the Kerberos Agent with the appropriate configuration. Review the `kerberos-agent-deployment.yaml` file and adjust the relevant settings, such as the RTSP URL, to ensure proper functionality. As mentioned below note that you can opt for the [Kerberos Factory](https://github.com/kerberos-io/factory/tree/master/kubernetes) which gives you a UI to manage the creation of Kerberos Agents. **_(Please note if you generated new the keys in the previous Kerberos Vault account creation, you need to update those in the Kerberos Agent deployment)_**
After deploying the Vault and configuring the necessary services for storage, database, and integration, you can proceed to deploy the Agent with the appropriate configuration. Review the `kerberos-agent-deployment.yaml` file and adjust the relevant settings, such as the RTSP URL, to ensure proper functionality. As mentioned below note that you can opt for the [Factory](https://github.com/kerberos-io/factory/tree/master/kubernetes) which gives you a UI to manage the creation of Agents. **_(Please note if you generated new the keys in the previous Vault account creation, you need to update those in the Agent deployment)_**
```bash
kubectl apply -f kerberos-agent-deployment.yaml
```
Review the creation of the Kerberos Agent and review the logs of the container to validate the Kerberos Agent is able to connect to the IP camera, and if a recording is being created and transferred to the Kerberos Vault
Review the creation of the Agent and review the logs of the container to validate the Agent is able to connect to the IP camera, and if a recording is being created and transferred to the Vault
```bash
kubectl get po -w -A
kubectl logs -f kerberos-agent...
```
To validate the Kerberos Vault and review any stored recordings, access the user interface at `http://localhost:30080` (after establishing the reverse tunnel).
To validate the Vault and review any stored recordings, access the user interface at `http://localhost:30080` (after establishing the reverse tunnel).
### Create Kerberos Agents through Kerberos Factory
### Create Agents through Factory
Managing Kerberos Agents through seperate configuration files might feel cumbersome, especially for non-technical users. This is where Kerberos Factory comes into the picture. Kerberos Factory provides a visual view that allows you to rapidly connect cameras through a user interface, which allows users without any technical background about cameras and kubernetes create Kerberos Agents.
Managing Agents through seperate configuration files might feel cumbersome, especially for non-technical users. This is where Factory comes into the picture. Factory provides a visual view that allows you to rapidly connect cameras through a user interface, which allows users without any technical background about cameras and kubernetes create Agents.
Kerberos Factory also requires a mongodb, just like Kerberos Vault. Luckily you can reuse the mongodb installation we have deployed earlier, the only thing we'll need to do is to create another `configmap.yaml` in the `kerberos-factory` namespace.
Factory also requires a mongodb, just like Vault. Luckily you can reuse the mongodb installation we have deployed earlier, the only thing we'll need to do is to create another `configmap.yaml` in the `kerberos-factory` namespace.
Create the `kerberos-factory` namespace.
@@ -378,7 +397,7 @@ Create the `kerberos-factory` namespace.
kubectl create namespace kerberos-factory
```
Apply the manifests, so the Kerberos Factory application is deployed and knows how to connect to the MongoDB.
Apply the manifests, so the Factory application is deployed and knows how to connect to the MongoDB.
```bash
kubectl apply -f ./mongodb-configmap.yaml -n kerberos-factory
@@ -386,7 +405,7 @@ kubectl apply -f ./kerberos-factory-deployment.yaml -n kerberos-factory
kubectl apply -f ./kerberos-factory-service.yaml -n kerberos-factory
```
To allow our Kerberos Factory to create Kubernetes resources we will need to apply an additional cluster role. This will allow our Kerberos Factory deployment to read and write resources to our Kubernetes cluster.
To allow our Factory to create Kubernetes resources we will need to apply an additional cluster role. This will allow our Factory deployment to read and write resources to our Kubernetes cluster.
```bash
kubectl apply -f ./kerberos-factory-clusterrole.yaml -n kerberos-factory
@@ -400,9 +419,9 @@ kubectl get po -w -A
### Optimized Data Filtering for Enhanced Bandwidth Efficiency and Relevance
Once your Kerberos Agents are properly connected and all recordings are stored in the Kerberos Vault, you may encounter additional challenges such as bandwidth limitations, storage constraints, and the need to efficiently locate relevant data. To accomplish this, we can configure an integration to filter the recordings, ensuring that only the relevant ones are retained.
Once your Agents are properly connected and all recordings are stored in the Vault, you may encounter additional challenges such as bandwidth limitations, storage constraints, and the need to efficiently locate relevant data. To accomplish this, we can configure an integration to filter the recordings, ensuring that only the relevant ones are retained.
Assuming all configurations are correctly set and all Kubernetes deployments are operational, you can apply the `data-filtering-deployment.yaml` deployment. This deployment will schedule a pod that listens to the configured integration in Kerberos Vault and runs a YOLOv8 model to evaluate the recordings and match them against specified conditions.
Assuming all configurations are correctly set and all Kubernetes deployments are operational, you can apply the `data-filtering-deployment.yaml` deployment. This deployment will schedule a pod that listens to the configured integration in Vault and runs a YOLOv8 model to evaluate the recordings and match them against specified conditions.
Please note that if you do not have a GPU on the device, you will need to disable the resource limit of the nvidia/gpu. Once done the filtering will run on the CPU.
@@ -418,7 +437,7 @@ Let's deploy the data filtering pod (with or without GPU support).
kubectl apply -f data-filtering-deployment.yaml
```
Each time a recording is stored in the Kerberos Vault, the `data-filtering` pod will receive a notification and execute the specified model (YOLOv8 by default). Based on the defined conditions, the `data-filtering` pod may forward the recording to a remote Kerberos Vault, trigger alerts, or send notifications.
Each time a recording is stored in the Vault, the `data-filtering` pod will receive a notification and execute the specified model (YOLOv8 by default). Based on the defined conditions, the `data-filtering` pod may forward the recording to a remote Vault, trigger alerts, or send notifications.
Ensure that the `data-filtering` workload is actively running, receiving messages from the integration, and performing the necessary processing tasks.
@@ -427,7 +446,7 @@ kubectl get po -w -A
kubectl logs -f data...
```
You might see something like below, whereas the `data-filtering` pod is iterating over recordings and frames, looking for a person. Once it finds the person it will try to `forward` the recording to a remote Kerberos Vault.
You might see something like below, whereas the `data-filtering` pod is iterating over recordings and frames, looking for a person. Once it finds the person it will try to `forward` the recording to a remote Vault.
```bash
Persons: 0, Cars: 0, Trucks: 0
@@ -449,34 +468,34 @@ Delete media from http://vault-lb.kerberos-vault/api
8) Releasing video writer and closing video capture
```
As indicated by the logs `Something went wrong while forwarding media`, the forwarding process failed due to the absence of an integration between the two `Kerberos Vaults`. Currently, only one `Kerberos Vault` is available. To enable this feature, you will need to [install a second `Kerberos Vault` in the cloud](./README.k8s-managed.md) with access to cloud storage.
As indicated by the logs `Something went wrong while forwarding media`, the forwarding process failed due to the absence of an integration between the two `Vaults`. Currently, only one `Vault` is available. To enable this feature, you will need to [install a second `Vault` in the cloud](./README.k8s-managed.md) with access to cloud storage.
### Add forwarding integration
If you have setup a secondary Kerberos Vault in the cloud, attached cloud Object storage to it, we can continue and add an additional integration through the UI.
If you have setup a secondary Vault in the cloud, attached cloud Object storage to it, we can continue and add an additional integration through the UI.
```bash
ssh -L 8080:localhost:30080 user@server-ip -p 22
```
Navigate to the `Kerberos Vault` application in your browser, access the `Integration` section, and add a new integration. This integration will connect your local Kerberos Vault to the remote Kerberos Vault, and will tell the system to set recordings in a `forwarding state`.
Navigate to the `Vault` application in your browser, access the `Integration` section, and add a new integration. This integration will connect your local Vault to the remote Vault, and will tell the system to set recordings in a `forwarding state`.
- Add an integration
- Kerberos Vault
- Vault
- Enabled: true
- Integration name: remote-vault
- Forwarding mode: continuous
- Url: http(s)://yourvault.com/api
- Provider: The name of the remote storage provider
- Access Key: The access key of the account you have created on the remote Kerberos Vault
- Secret Access Key: The secret access key of the account you have created on the remote Kerberos Vault
- Access Key: The access key of the account you have created on the remote Vault
- Secret Access Key: The secret access key of the account you have created on the remote Vault
If the integration is functioning correctly, you should observe that recordings are initially marked in gray as "To be forwarded." After a short period, some recordings will be updated to green, indicating they have been "Forwarded by."
## Cleanup
If you consider to remove the Kerberos.io stack you might just disable the microk8s installation
If you consider to remove the ecosystem you might just disable the microk8s installation
```bash
microk8s reset
+9 -3
View File
@@ -86,7 +86,6 @@ microk8s enable dns
microk8s enable dashboard
microk8s enable nvidia
microk8s enable hostpath-storage
microk8s enable minio
```
You can verify the status of the enabled addons by running the following command:
@@ -103,7 +102,7 @@ kubectl get po -w -A
### Clone repository
Next, we will clone this repository to our local environment. This will allow us to execute the necessary configuration files for installing the Minio operator, MongoDB Helm chart, and other required components.
Next, clone this repository for the Garage S3 manifests, MongoDB Helm chart values, and other required components.
```bash
git clone https://github.com/kerberos-io/deployment
@@ -112,6 +111,13 @@ cd deployment
## Kustomize
The default base uses [Garage S3](./base/garage/README.md). Before a native
Kustomize apply, run `bash scripts/install-garage.sh --prepare-only` to create
its namespace and persistent credentials. `configure.sh apply` does this
automatically. Do not enable the MinIO addon for this default stack. Existing
MinIO installations must keep their backend/provider settings until their data
is explicitly migrated; do not prune their storage resources.
In contrast to the detailed installation instructions, as mentioned here, an easier option to install is to use our Kustomize configure. This will allow you to specify and create your own overlays to install all the different components through a single command line.
Kustomize uses the concept of `bases` and `overlays`, allowing you to customize the base installation with different settings (an overlay). Before executing the `kustomize` command below, navigate to the `overlays/microk8s/kustomization.yaml` file and modify the `inlineValues` of the Hub to match the IP address of your node. Please note that when using Multipass, WSL, or any other type of virtualization, your IP address will differ from the IP address of your host machine. Verify your IP address using the `ifconfig` command.
@@ -185,7 +191,7 @@ namespace/kerberos-agent unchanged
namespace/kerberos-factory unchanged
namespace/kerberos-hub unchanged
namespace/kerberos-vault unchanged
namespace/minio-tenant unchanged
namespace/garage unchanged
namespace/mongodb unchanged
namespace/rabbitmq unchanged
namespace/vernemq unchanged
+120 -18
View File
@@ -1,25 +1,70 @@
# Deployment
Deploying the Kerberos.io stack may initially appear to be a complex task due to its design for scalability from the outset. This entails the integration of several scalable components within a distributed deployment architecture. To streamline the installation process, we have established this repository to offer comprehensive guidance. Our objective is to incorporate as much automation as possible within this repository, thereby ensuring that the installation process is straightforward and user-friendly. At the same time, we remain committed to delivering a scalable and highly available video surveillance system.
Deploying the ecosystem may initially appear to be a complex task due to its design for scalability from the outset. This entails the integration of several scalable components within a distributed deployment architecture. To streamline the installation process, we have established this repository to offer comprehensive guidance. Our objective is to incorporate as much automation as possible within this repository, thereby ensuring that the installation process is straightforward and user-friendly. At the same time, we remain committed to delivering a scalable and highly available video surveillance system.
![How it works](./assets/images/how-it-works.svg)
[![How it works: cameras feed Agents, Factory manages Agents on Kubernetes, Vault stores recordings, and Hub analyses events.](./assets/images/how-it-works.svg)][draw-how-it-works]
The Kerberos.io stack offers flexible installation options, supporting deployment in hybrid environments, fully in the cloud, or entirely at the edge. This deployment guide covers the installation of various Kerberos.io components. For detailed information on each component, please refer to their respective repositories.
[Edit in Draw][draw-how-it-works] · [Diagram source](./assets/diagrams/how-it-works.json)
- [Kerberos Agent](https://github.com/kerberos-io/agent)
- [Kerberos Factory](https://github.com/kerberos-io/factory)
- [Kerberos Vault](https://github.com/kerberos-io/vault)
- [Kerberos Hub](https://github.com/kerberos-io/hub)
The ecosystem offers flexible installation options, supporting deployment in hybrid environments, fully in the cloud, or entirely at the edge. This deployment guide covers installing its components. For detailed component documentation, please refer to the links below.
- [Agent](https://learn.uug.ai/docs/agent/)
- [Factory](https://learn.uug.ai/docs/factory/)
- [Vault](https://github.com/kerberos-io/vault)
- [Hub](https://github.com/kerberos-io/hub)
## Continuous deployment
[<img src="https://github.com/kerberos-io/deployment/workflows/Deploy%20on%20Microk8s/badge.svg"/>](https://github.com/kerberos-io/deployment/actions/workflows/microk8s.yaml) [<img src="https://github.com/kerberos-io/deployment/workflows/Deploy%20on%20Kubernetes/badge.svg"/>](https://github.com/kerberos-io/deployment/actions/workflows/kind.yaml)
[![Deploy on kind](https://github.com/uug-ai/deployment/actions/workflows/kind.yaml/badge.svg?branch=main)](https://github.com/uug-ai/deployment/actions/workflows/kind.yaml)
[![Deploy on k3d](https://github.com/uug-ai/deployment/actions/workflows/k3d.yaml/badge.svg?branch=main)](https://github.com/uug-ai/deployment/actions/workflows/k3d.yaml)
[![Deploy on MicroK8s](https://github.com/uug-ai/deployment/actions/workflows/microk8s.yaml/badge.svg?branch=main)](https://github.com/uug-ai/deployment/actions/workflows/microk8s.yaml)
[![Validate Kustomize, Garage and Factory on kind](https://github.com/uug-ai/deployment/actions/workflows/kustomize.yaml/badge.svg?branch=main)](https://github.com/uug-ai/deployment/actions/workflows/kustomize.yaml)
This repository continuously tests the Kerberos.io deployment using GitHub Actions across various platforms. Refer to the badges for an overview of the operating systems and Kubernetes distributions we are deploying to. We welcome suggestions for additional automations to consider.
These badges show the latest status of each GitHub Actions workflow on `main`.
Select a badge to view its runs and platform-specific results. The deployment
workflows continuously test the ecosystem across kind, k3d, and MicroK8s; the
Kustomize workflow validates the Kustomize, Garage, and Factory setup.
Kubernetes examples and CI now use **Garage S3** for local object storage.
See the [Garage setup guide](./base/garage/README.md) for generated credentials,
Vault configuration, persistence tests and existing MinIO compatibility.
This is a single-node example, not a replicated production storage deployment.
The [MinIO manifests](./base/minio/) remain available; no existing data is
migrated or removed automatically. The Docker guide retains its separate
storage setup.
Slack notifications are optional. If `SLACK_WEBHOOK_URL` is unavailable (for
example on fork PRs), CI emits a warning and skips delivery without masking
deployment failures. Configured webhook delivery errors still fail the step.
Failed deployment jobs collect pod events and bounded logs for diagnosis.
### Hardened Hub API and frontend
The Kubernetes overlays and full deployment workflows pin Hub chart **0.170.1**.
It supplies Hub API container/pod security contexts for the promoted
`ghcr.io/uug-ai/hub-api:v1.11.31`: UID/GID `65532`, read-only root filesystem,
no privilege escalation, dropped capabilities and RuntimeDefault seccomp.
A disk-backed `hub-api-tmp` emptyDir at `/tmp` provides writable upload scratch
space with pod `fsGroup: 65532`; it is not persistent storage.
Retain this volume and mount when adding custom API mounts, because Helm
replaces lists rather than appending to them.
The promoted frontend image requires Hub chart **0.170.0 or newer**, which
provides port `8080`, non-root/read-only runtime settings and a writable `/tmp`
mount. Both primary and demo frontends use these settings; Admin remains on its
legacy image and port `80`. Keep `global.imageRegistry` empty for the supplied
fully qualified `ghcr.io` image names. Service ports remain `80` and the frontend
NodePort remains `32080`, targeting the container port named `http`.
Run `bash scripts/check-hub-runtime.sh` to check published chart `0.170.1` with these
values before installation, or pass a local chart path as the first argument.
When adding custom branding mounts, preserve the chart's `frontend-tmp` volume
and `/tmp` mount. Existing deployments may require the explicit
[HTTP port migration](https://github.com/kerberos-io/helm-charts/blob/main/charts/hub/README.md#recovering-a-duplicate-http-port-during-upgrades).
## Architecture
As previously indicated, several integral components, including Object Storage, Database, and Message Brokers, are deployed and configured to ensure the proper functionality of Kerberos.io. The architecture diagrams below aims to provide a visual representation of these components and their interactions with one another depending the deployment; self-hosted or managed cloud deployment.
As previously indicated, several integral components, including object storage, databases, and message brokers, are deployed and configured to support the ecosystem. The architecture diagrams below provide a visual representation of these components and how they interact in self-hosted and managed cloud deployments.
It is important to note that the ease of installation may vary depending on the chosen Kubernetes distribution and platform. For instance, distributions like Microk8s, which offer an addons feature, may simplify the installation process significantly. Managed Kubernetes services on platforms such as AWS, Azure, GCP, and others offer a range of supplementary services. These include, but are not limited to, cloud-based object storage, load balancers, persistent volumes, and additional resources that enhance the deployment experience. On the other hand running a self-hosted vanilla Kubernetes deployment will come with additinal work as it misses the out-of-the-box components from a managed deployment or the self-hosted Microk8s add-ons feature.
@@ -27,17 +72,28 @@ Given these differences, we have created specific architectural frameworks for s
## Edge (self-hosted) deployment
Edge (self-hosted) deployments are typically used for camera processing and edge storage. In this setup, Kerberos Agents are deployed and connected to cameras, with recordings stored in the Kerberos Vault. Additionally, you may want to create integrations, such as [data filtering](https://github.com/uug-ai/data-filtering), to ensure only relevant recordings are retained, or set up custom notifications to your first or third-party platforms. In this edge scenario, hardware is being deployed in the local network to handle the workloads; for example AMD64 or ARM64 processors.
Edge (self-hosted) deployments are typically used for camera processing and edge storage. In this setup, Agents are deployed and connected to cameras, with recordings stored in the Vault. Additionally, you may want to create integrations, such as [data filtering](https://github.com/uug-ai/data-filtering), to ensure only relevant recordings are retained, or set up custom notifications to your first or third-party platforms. In this edge scenario, hardware is being deployed in the local network to handle the workloads; for example AMD64 or ARM64 processors.
![Edge - self-hosted deployment](./assets/images/deployment-self-hosted.svg)
[![Self-hosted deployment: one Agent per camera uploads to Vault, backed by MongoDB and object storage. Optional RabbitMQ events feed custom integrations.](./assets/images/deployment-self-hosted.svg)][draw-deployment-self-hosted]
A key aspect of this deployment is the scaling of Kerberos Agents. Each camera is assigned a dedicated Kerberos Agent container, which is linked to a central Kerberos Vault. The Kerberos Vault stores metadata in MongoDB, recordings in Minio (or another S3-compliant object storage), and can generate events in a message broker like RabbitMQ. This approach enables you to develop custom applications/logic that can respond to the creation of new recordings.
[Edit in Draw][draw-deployment-self-hosted] · [Diagram source](./assets/diagrams/deployment-self-hosted.json)
Factory is optional and manages Agents when using Kubernetes. Event publishing
and custom integrations are optional; recordings can stay entirely within the
private edge network.
A key aspect of this deployment is the scaling of Agents. Each camera is
assigned a dedicated Agent container, which is linked to a central Vault. Vault
stores metadata in MongoDB, recordings in Garage (or another S3-compatible
object storage), and can generate events in a message broker like RabbitMQ.
This approach enables you to develop custom applications or logic that can
respond to new recordings.
Based on your technology experience and preferences, you can choose from the following deployment guides:
- [[Easy] Install Kerberos.io on Docker](/README.docker.md)
- [[Medium] Install Kerberos.io on Microk8s](/README.microk8s.md)
- [[Advanced] Install Kerberos.io on Kubernetes](/README.k8s.md)
- [[Easy] Install the ecosystem with Docker](/README.docker.md)
- [[Medium] Install the ecosystem with Microk8s](/README.microk8s.md)
- [[Advanced] Install the ecosystem with Kubernetes](/README.k8s.md)
## Managed deployment
@@ -59,8 +115,54 @@ As previously discussed, a managed deployment enhances resilience and high avail
The primary objective of maintaining a managed, public-facing deployment is to centralize multiple edge deployments through a unified interface. Typically, edge deployments are distributed across various environments such as hospitals, retail stores, and factories. These edge deployments are inherently secure and non-public facing, preventing direct internet access to these environments. To address this challenge, we operate a managed, public-facing environment that consolidates all recordings, live camera views, and permissions into a single, cohesive platform.
![Managed deployment](./assets/images/deployment-managed.svg)
[![Managed deployment: Agents at private sites upload over HTTPS to cloud Vault. Object storage holds recordings, MongoDB holds metadata, and RabbitMQ feeds Hub processing.](./assets/images/deployment-managed.svg)][draw-deployment-managed]
[Edit in Draw][draw-deployment-managed] · [Diagram source](./assets/diagrams/deployment-managed.json)
This example shows Agents uploading directly to cloud Vault, without exposing
the camera networks to inbound internet traffic. Hybrid deployments can retain
an edge Vault and forward selected recordings to cloud Vault instead. The
backing services can be self-hosted or compatible managed services; their logical
roles stay the same.
Based on your technology experience and preferences, you can choose from the following deployment guides:
- [[Medium] Install Kerberos.io on Kubernetes (AWS, GCP, Azure, etc.)](/README.k8s-managed.md)
- [[Medium] Install the ecosystem with Kubernetes (AWS, GCP, Azure, etc.)](/README.k8s-managed.md)
Or provision the infrastructure yourself with infrastructure as code:
- [[AWS] Terraform: EKS cluster + Amazon DocumentDB](./modules/amazon-eks-documentdb/README.md)
- [[Azure] Terraform: Event Hub with 10 partitions](./modules/azure-event-hub/README.md)
## Updating architecture diagrams
These diagrams use the same editable `groups`, `nodes`, and `connections` JSON
format as the [Learn diagrams](https://learn.uug.ai/docs/prologue/how/) and
[Draw](https://draw.uug.ai/). GitHub cannot run Learn's interactive renderer, so
the README uses self-contained SVG exports from Draw, with an **Edit in Draw**
link carrying the source in a base64-encoded UTF-8 URL fragment.
1. Update the corresponding JSON in [assets/diagrams](./assets/diagrams).
Keep the product colours, labelled zones, cards and orthogonal connectors
consistent with Learn; the overview follows Learn's solution stack.
2. Run `node scripts/update-diagram-links.mjs` (Node.js 20+) to refresh the
README's Draw links. Open the updated **Edit in Draw** link, or paste the
JSON into Draw's **Templates** import.
3. Use Draw's **Export board** action to download an SVG in light mode and
replace the matching file in [assets/images](./assets/images). Export the
whole diagram, not a screenshot of the editor. Draw embeds its image assets
so GitHub does not need external icons or scripts.
4. Run `node scripts/update-diagram-links.mjs --check` and preview the README.
The check validates source bounds/connections, SVG presence and link
freshness; also check the exported diagram visually.
Edits on a Draw board do not automatically update this repository. Mirror any
board changes into the JSON source before refreshing the links and SVG. Commit
the source, preview and README links together; do not hand-edit the generated
links below.
<!-- BEGIN GENERATED DRAW LINKS -->
[draw-how-it-works]: https://draw.uug.ai/#diagram=eyJncm91cHMiOlt7ImlkIjoiY2FwdHVyZSIsImxhYmVsIjoiMS4gQ2FtZXJhIHByb2Nlc3NpbmcgLSBlZGdlIC8gY2xvdWQiLCJ4IjowLCJ5IjoyMCwidyI6NTQwLCJoIjo1MjB9LHsiaWQiOiJwZXJzaXN0ZW5jZSIsImxhYmVsIjoiMi4gUGVyc2lzdGVuY2UgLSBlZGdlIC8gY2xvdWQiLCJ4Ijo2MjAsInkiOjIwLCJ3IjozMDAsImgiOjUyMH0seyJpZCI6ImFuYWx5c2lzIiwibGFiZWwiOiIzLiBBbmFseXNlIC0gZWRnZSAvIGNsb3VkIiwieCI6MTAwMCwieSI6MjAsInciOjMwMCwiaCI6NTIwfV0sIm5vZGVzIjpbeyJpZCI6ImNhbWVyYS0xIiwia2luZCI6ImNhbWVyYSIsIngiOjMwLCJ5IjoyMzAsInciOjE4MCwiaCI6MTQwLCJoZWFkZXIiOiJDQU1FUkEiLCJ0aXRsZSI6IkNhbWVyYSAxIiwic3VidGl0bGUiOiJSVFNQIHN0cmVhbSJ9LHsiaWQiOiJjYW1lcmEtMiIsImtpbmQiOiJjYW1lcmEiLCJ4IjozMCwieSI6MzkwLCJ3IjoxODAsImgiOjE0MCwiaGVhZGVyIjoiQ0FNRVJBIiwidGl0bGUiOiJDYW1lcmEgMiIsInN1YnRpdGxlIjoiUlRTUCBzdHJlYW0ifSx7ImlkIjoiZmFjdG9yeSIsImtpbmQiOiJmYWN0b3J5IiwieCI6MjgwLCJ5Ijo2MCwidyI6MjMwLCJoIjoxNDAsImhlYWRlciI6Ik9SQ0hFU1RSQVRFIiwidGl0bGUiOiJGYWN0b3J5Iiwic3VidGl0bGUiOiJNYW5hZ2Ugb24gS3ViZXJuZXRlcyJ9LHsiaWQiOiJhZ2VudC0xIiwia2luZCI6ImFnZW50IiwieCI6MjgwLCJ5IjoyMzAsInciOjIzMCwiaCI6MTQwLCJoZWFkZXIiOiJSRUNPUkQiLCJ0aXRsZSI6IkFnZW50IDEiLCJzdWJ0aXRsZSI6IlByb2Nlc3Mgb25lIGNhbWVyYSJ9LHsiaWQiOiJhZ2VudC0yIiwia2luZCI6ImFnZW50IiwieCI6MjgwLCJ5IjozOTAsInciOjIzMCwiaCI6MTQwLCJoZWFkZXIiOiJSRUNPUkQiLCJ0aXRsZSI6IkFnZW50IDIiLCJzdWJ0aXRsZSI6IlByb2Nlc3Mgb25lIGNhbWVyYSJ9LHsiaWQiOiJ2YXVsdCIsImtpbmQiOiJ2YXVsdCIsIngiOjY1MCwieSI6MjMwLCJ3IjoyNDAsImgiOjE0MCwiaGVhZGVyIjoiU1RPUkUiLCJ0aXRsZSI6IlZhdWx0Iiwic3VidGl0bGUiOiJTdG9yZSBhbmQgaW50ZWdyYXRlIn0seyJpZCI6InN0b3JhZ2UiLCJraW5kIjoic3RvcmFnZSIsIngiOjY1MCwieSI6MzkwLCJ3IjoyNDAsImgiOjE0MCwiaGVhZGVyIjoiT0JKRUNUIFNUT1JBR0UiLCJ0aXRsZSI6IlN0b3JhZ2UgYmFja2VuZCIsInN1YnRpdGxlIjoiQ2xvdWQgLyBlZGdlIn0seyJpZCI6Imh1YiIsImtpbmQiOiJodWIiLCJ4IjoxMDMwLCJ5IjoyMzAsInciOjI0MCwiaCI6MTQwLCJoZWFkZXIiOiJBTkFMWVNFIiwidGl0bGUiOiJIdWIiLCJzdWJ0aXRsZSI6Ik1vbml0b3IgYW5kIGFuYWx5c2UifV0sImNvbm5lY3Rpb25zIjpbeyJmcm9tIjoiY2FtZXJhLTEiLCJ0byI6ImFnZW50LTEiLCJmcm9tU2lkZSI6InJpZ2h0IiwidG9TaWRlIjoibGVmdCJ9LHsiZnJvbSI6ImNhbWVyYS0yIiwidG8iOiJhZ2VudC0yIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQifSx7ImZyb20iOiJmYWN0b3J5IiwidG8iOiJhZ2VudC0xIiwiZnJvbVNpZGUiOiJib3R0b20iLCJ0b1NpZGUiOiJ0b3AiLCJraW5kIjoiZGFzaGVkIn0seyJmcm9tIjoiZmFjdG9yeSIsInRvIjoiYWdlbnQtMiIsImZyb21TaWRlIjoicmlnaHQiLCJ0b1NpZGUiOiJyaWdodCIsImtpbmQiOiJkYXNoZWQifSx7ImZyb20iOiJhZ2VudC0xIiwidG8iOiJ2YXVsdCIsImZyb21TaWRlIjoicmlnaHQiLCJ0b1NpZGUiOiJsZWZ0In0seyJmcm9tIjoiYWdlbnQtMiIsInRvIjoidmF1bHQiLCJmcm9tU2lkZSI6InJpZ2h0IiwidG9TaWRlIjoibGVmdCJ9LHsiZnJvbSI6InZhdWx0IiwidG8iOiJzdG9yYWdlIiwiZnJvbVNpZGUiOiJib3R0b20iLCJ0b1NpZGUiOiJ0b3AifSx7ImZyb20iOiJ2YXVsdCIsInRvIjoiaHViIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQiLCJsYWJlbCI6IkV2ZW50cyJ9XX0=
[draw-deployment-self-hosted]: https://draw.uug.ai/#diagram=eyJncm91cHMiOlt7ImlkIjoiZWRnZSIsImxhYmVsIjoiUHJpdmF0ZSBlZGdlIG5ldHdvcmsgLSBEb2NrZXIgLyBLdWJlcm5ldGVzIiwieCI6MCwieSI6MjAsInciOjEyNDAsImgiOjY4MH1dLCJub2RlcyI6W3siaWQiOiJjYW1lcmEtMSIsImtpbmQiOiJjYW1lcmEiLCJ4IjozMCwieSI6MjgwLCJ3IjoxODAsImgiOjE0MCwiaGVhZGVyIjoiQ0FNRVJBIiwidGl0bGUiOiJDYW1lcmEgMSIsInN1YnRpdGxlIjoiUlRTUCBzdHJlYW0ifSx7ImlkIjoiY2FtZXJhLTIiLCJraW5kIjoiY2FtZXJhIiwieCI6MzAsInkiOjUxMCwidyI6MTgwLCJoIjoxNDAsImhlYWRlciI6IkNBTUVSQSIsInRpdGxlIjoiQ2FtZXJhIDIiLCJzdWJ0aXRsZSI6IlJUU1Agc3RyZWFtIn0seyJpZCI6ImZhY3RvcnkiLCJraW5kIjoiZmFjdG9yeSIsIngiOjI4MCwieSI6ODAsInciOjIyMCwiaCI6MTQwLCJoZWFkZXIiOiJPUFRJT05BTCBPUkNIRVNUUkFUSU9OIiwidGl0bGUiOiJGYWN0b3J5Iiwic3VidGl0bGUiOiJNYW5hZ2Ugb24gS3ViZXJuZXRlcyJ9LHsiaWQiOiJhZ2VudC0xIiwia2luZCI6ImFnZW50IiwieCI6MjgwLCJ5IjoyODAsInciOjIyMCwiaCI6MTQwLCJoZWFkZXIiOiJSRUNPUkQiLCJ0aXRsZSI6IkFnZW50IDEiLCJzdWJ0aXRsZSI6Ik9uZSBjYW1lcmEgcGVyIGNvbnRhaW5lciJ9LHsiaWQiOiJhZ2VudC0yIiwia2luZCI6ImFnZW50IiwieCI6MjgwLCJ5Ijo1MTAsInciOjIyMCwiaCI6MTQwLCJoZWFkZXIiOiJSRUNPUkQiLCJ0aXRsZSI6IkFnZW50IDIiLCJzdWJ0aXRsZSI6Ik9uZSBjYW1lcmEgcGVyIGNvbnRhaW5lciJ9LHsiaWQiOiJ2YXVsdCIsImtpbmQiOiJ2YXVsdCIsIngiOjYyMCwieSI6MjgwLCJ3IjoyMzAsImgiOjE0MCwiaGVhZGVyIjoiU1RPUkUiLCJ0aXRsZSI6IlZhdWx0Iiwic3VidGl0bGUiOiJDZW50cmFsaXNlIGVkZ2UgcmVjb3JkaW5ncyJ9LHsiaWQiOiJtb25nb2RiIiwia2luZCI6ImRhdGFiYXNlIiwieCI6OTcwLCJ5Ijo4MCwidyI6MjQwLCJoIjoxNDAsImhlYWRlciI6Ik1FVEFEQVRBIiwidGl0bGUiOiJNb25nb0RCIiwic3VidGl0bGUiOiJWYXVsdCBtZXRhZGF0YSJ9LHsiaWQiOiJzdG9yYWdlIiwia2luZCI6InN0b3JhZ2UiLCJ4Ijo5NzAsInkiOjI4MCwidyI6MjQwLCJoIjoxNDAsImhlYWRlciI6IlJFQ09SRElOR1MiLCJ0aXRsZSI6Ik9iamVjdCBzdG9yYWdlIiwic3VidGl0bGUiOiJNaW5JTyAvIFMzLWNvbXBhdGlibGUifSx7ImlkIjoiYnJva2VyIiwia2luZCI6ImFtcXAiLCJ4Ijo5NzAsInkiOjUxMCwidyI6MjQwLCJoIjoxNDAsImhlYWRlciI6Ik9QVElPTkFMIEVWRU5UUyIsInRpdGxlIjoiUmFiYml0TVEiLCJzdWJ0aXRsZSI6IlJlY29yZGluZyBldmVudHMifSx7ImlkIjoiaW50ZWdyYXRpb24iLCJraW5kIjoiZGVmYXVsdCIsIngiOjYyMCwieSI6NTEwLCJ3IjoyMzAsImgiOjE0MCwiaGVhZGVyIjoiT1BUSU9OQUwgSU5URUdSQVRJT04iLCJ0aXRsZSI6IkN1c3RvbSBhcHBsaWNhdGlvbiIsInN1YnRpdGxlIjoiRmlsdGVyLCBub3RpZnkgb3IgaW50ZWdyYXRlIn1dLCJjb25uZWN0aW9ucyI6W3siZnJvbSI6ImNhbWVyYS0xIiwidG8iOiJhZ2VudC0xIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQiLCJsYWJlbCI6IlJUU1AifSx7ImZyb20iOiJjYW1lcmEtMiIsInRvIjoiYWdlbnQtMiIsImZyb21TaWRlIjoicmlnaHQiLCJ0b1NpZGUiOiJsZWZ0IiwibGFiZWwiOiJSVFNQIn0seyJmcm9tIjoiZmFjdG9yeSIsInRvIjoiYWdlbnQtMSIsImZyb21TaWRlIjoiYm90dG9tIiwidG9TaWRlIjoidG9wIiwia2luZCI6ImRhc2hlZCJ9LHsiZnJvbSI6ImZhY3RvcnkiLCJ0byI6ImFnZW50LTIiLCJmcm9tU2lkZSI6ImxlZnQiLCJ0b1NpZGUiOiJsZWZ0Iiwia2luZCI6ImRhc2hlZCJ9LHsiZnJvbSI6ImFnZW50LTIiLCJ0byI6InZhdWx0IiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQifSx7ImZyb20iOiJhZ2VudC0xIiwidG8iOiJ2YXVsdCIsImZyb21TaWRlIjoicmlnaHQiLCJ0b1NpZGUiOiJsZWZ0IiwibGFiZWwiOiJVcGxvYWQifSx7ImZyb20iOiJ2YXVsdCIsInRvIjoibW9uZ29kYiIsImZyb21TaWRlIjoidG9wIiwidG9TaWRlIjoibGVmdCJ9LHsiZnJvbSI6InZhdWx0IiwidG8iOiJzdG9yYWdlIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQifSx7ImZyb20iOiJ2YXVsdCIsInRvIjoiYnJva2VyIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQiLCJsYWJlbCI6IkV2ZW50cyJ9LHsiZnJvbSI6ImJyb2tlciIsInRvIjoiaW50ZWdyYXRpb24iLCJmcm9tU2lkZSI6ImxlZnQiLCJ0b1NpZGUiOiJyaWdodCIsImxhYmVsIjoiQ29uc3VtZSJ9XX0=
[draw-deployment-managed]: https://draw.uug.ai/#diagram=eyJncm91cHMiOlt7ImlkIjoiZWRnZSIsImxhYmVsIjoiUHJpdmF0ZSBlZGdlIHNpdGVzIC0gb3V0Ym91bmQgdXBsb2FkcyIsIngiOjAsInkiOjIwLCJ3Ijo1MDAsImgiOjc2MH0seyJpZCI6ImNsb3VkIiwibGFiZWwiOiJDbG91ZCAtIG1hbmFnZWQgS3ViZXJuZXRlcyBhbmQgYmFja2luZyBzZXJ2aWNlcyIsIngiOjYyMCwieSI6MjAsInciOjcwMCwiaCI6NzYwfV0sIm5vZGVzIjpbeyJpZCI6ImNhbWVyYS0xIiwia2luZCI6ImNhbWVyYSIsIngiOjMwLCJ5IjoxNzAsInciOjE4MCwiaCI6MTQwLCJoZWFkZXIiOiJTSVRFIEEiLCJ0aXRsZSI6IkNhbWVyYXMiLCJzdWJ0aXRsZSI6IlByaXZhdGUgbmV0d29yayJ9LHsiaWQiOiJjYW1lcmEtMiIsImtpbmQiOiJjYW1lcmEiLCJ4IjozMCwieSI6NTMwLCJ3IjoxODAsImgiOjE0MCwiaGVhZGVyIjoiU0lURSBCIiwidGl0bGUiOiJDYW1lcmFzIiwic3VidGl0bGUiOiJQcml2YXRlIG5ldHdvcmsifSx7ImlkIjoiYWdlbnQtMSIsImtpbmQiOiJhZ2VudCIsIngiOjI3MCwieSI6MTcwLCJ3IjoyMDAsImgiOjE0MCwiaGVhZGVyIjoiUkVDT1JEIiwidGl0bGUiOiJBZ2VudHMiLCJzdWJ0aXRsZSI6Ik9uZSBwZXIgY2FtZXJhIn0seyJpZCI6ImFnZW50LTIiLCJraW5kIjoiYWdlbnQiLCJ4IjoyNzAsInkiOjUzMCwidyI6MjAwLCJoIjoxNDAsImhlYWRlciI6IlJFQ09SRCIsInRpdGxlIjoiQWdlbnRzIiwic3VidGl0bGUiOiJPbmUgcGVyIGNhbWVyYSJ9LHsiaWQiOiJ2YXVsdCIsImtpbmQiOiJ2YXVsdCIsIngiOjY1MCwieSI6MTcwLCJ3IjoyMzAsImgiOjE0MCwiaGVhZGVyIjoiU1RPUkUiLCJ0aXRsZSI6IlZhdWx0Iiwic3VidGl0bGUiOiJSZWNlaXZlIHJlY29yZGluZ3MifSx7ImlkIjoic3RvcmFnZSIsImtpbmQiOiJzdG9yYWdlIiwieCI6MTA0MCwieSI6MTcwLCJ3IjoyNTAsImgiOjE0MCwiaGVhZGVyIjoiUkVDT1JESU5HUyIsInRpdGxlIjoiT2JqZWN0IHN0b3JhZ2UiLCJzdWJ0aXRsZSI6IlMzIC8gQXp1cmUgQmxvYiAvIEdDUyJ9LHsiaWQiOiJicm9rZXIiLCJraW5kIjoiYW1xcCIsIngiOjY1MCwieSI6MzgwLCJ3IjoyMzAsImgiOjE0MCwiaGVhZGVyIjoiRVZFTlRTIiwidGl0bGUiOiJSYWJiaXRNUSIsInN1YnRpdGxlIjoiUXVldWUgcmVjb3JkaW5nIGV2ZW50cyJ9LHsiaWQiOiJtb25nb2RiIiwia2luZCI6ImRhdGFiYXNlIiwieCI6MTA0MCwieSI6MzgwLCJ3IjoyNTAsImgiOjE0MCwiaGVhZGVyIjoiTUVUQURBVEEiLCJ0aXRsZSI6Ik1vbmdvREIiLCJzdWJ0aXRsZSI6IlZhdWx0IC8gSHViIGRhdGFiYXNlcyJ9LHsiaWQiOiJodWIiLCJraW5kIjoiaHViIiwieCI6NjUwLCJ5Ijo2MDAsInciOjIzMCwiaCI6MTQwLCJoZWFkZXIiOiJBTkFMWVNFIiwidGl0bGUiOiJIdWIiLCJzdWJ0aXRsZSI6IlBpcGVsaW5lLCBBUEkgYW5kIFVJIn1dLCJjb25uZWN0aW9ucyI6W3siZnJvbSI6ImNhbWVyYS0xIiwidG8iOiJhZ2VudC0xIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQifSx7ImZyb20iOiJjYW1lcmEtMiIsInRvIjoiYWdlbnQtMiIsImZyb21TaWRlIjoicmlnaHQiLCJ0b1NpZGUiOiJsZWZ0In0seyJmcm9tIjoiYWdlbnQtMiIsInRvIjoidmF1bHQiLCJmcm9tU2lkZSI6InJpZ2h0IiwidG9TaWRlIjoibGVmdCJ9LHsiZnJvbSI6ImFnZW50LTEiLCJ0byI6InZhdWx0IiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQiLCJsYWJlbCI6IkhUVFBTIHVwbG9hZCJ9LHsiZnJvbSI6InZhdWx0IiwidG8iOiJtb25nb2RiIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImxlZnQifSx7ImZyb20iOiJ2YXVsdCIsInRvIjoic3RvcmFnZSIsImZyb21TaWRlIjoicmlnaHQiLCJ0b1NpZGUiOiJsZWZ0IiwibGFiZWwiOiJSZWNvcmRpbmdzIn0seyJmcm9tIjoidmF1bHQiLCJ0byI6ImJyb2tlciIsImZyb21TaWRlIjoiYm90dG9tIiwidG9TaWRlIjoidG9wIiwibGFiZWwiOiJFdmVudHMifSx7ImZyb20iOiJicm9rZXIiLCJ0byI6Imh1YiIsImZyb21TaWRlIjoiYm90dG9tIiwidG9TaWRlIjoidG9wIiwibGFiZWwiOiJQcm9jZXNzIn0seyJmcm9tIjoiaHViIiwidG8iOiJtb25nb2RiIiwiZnJvbVNpZGUiOiJyaWdodCIsInRvU2lkZSI6ImJvdHRvbSJ9XX0=
<!-- END GENERATED DRAW LINKS -->
+34 -15
View File
@@ -71,7 +71,7 @@ For more detailed instructions and troubleshooting, please refer to the official
## Dependencies
Before installing, several dependencies are required for storage, such as a database (e.g., MongoDB) and a message broker (e.g., RabbitMQ) for asynchronous behavior. We will install these components before setting up the Kerberos Agents and Kerberos Vault.
Before installing, several dependencies are required for storage, such as a database (e.g., MongoDB) and a message broker (e.g., RabbitMQ) for asynchronous behavior. We will install these components before setting up the Agents and Vault.
One of the key advantages of MicroK8s is its out-of-the-box addons, which can be enabled with a single command. This eliminates the need for complex Helm charts or operators, simplifying the setup process. We will enable some common services, such as DNS, GPU support, and storage, to streamline the installation.
@@ -96,14 +96,29 @@ kubectl get po -w -A
### Clone repository
Next, we will clone this repository to our local environment. This will allow us to execute the necessary configuration files for installing the Minio operator, MongoDB Helm chart, and other required components.
Next, clone this repository for the Garage S3 manifests, MongoDB Helm chart values, and other required components.
```bash
git clone https://github.com/kerberos-io/deployment
cd deployment
```
### Object storage: MinIO
### Object storage: Garage (default)
Create the `ssd-hostpath` StorageClass backed by MicroK8s hostpath storage:
```sh
kubectl apply -f base/ssd-hostpath-storageclass.yaml
bash scripts/install-garage.sh
bash scripts/check-garage.sh
```
See the [Garage guide](./base/garage/README.md) for generated credentials,
Vault settings and persistence. Garage uses S3 port `3900` internally and
NodePort `30090`; no MinIO operator or addon is required. Skip the following
legacy section when using the default installation.
### Optional legacy object storage: MinIO
MinIO is a high-performance, distributed object storage system that is compatible with Amazon S3 cloud storage service. It is designed to handle large-scale data storage and retrieval, making it an ideal choice for modern cloud-native applications.
@@ -189,7 +204,7 @@ To access the application, open your browser and navigate to `localhost:8080`. U
### Database: MongoDB
When using Vault, it will persist references to the recordings stored in your storage provider in a MongoDB database. As used before, we are using `helm` to install MongoDB in our Kubernetes cluster. Within the Kerberos Vault project we are using the latest official mongodb driver, so we support all major MongoDB versions (4.x, 5.x, 6.x, 7.x).
When using Vault, it will persist references to the recordings stored in your storage provider in a MongoDB database. As used before, we are using `helm` to install MongoDB in our Kubernetes cluster. Within the Vault project we are using the latest official mongodb driver, so we support all major MongoDB versions (4.x, 5.x, 6.x, 7.x).
Have a look into the `./mongodb-values.yaml` file, you will find plenty of configurations for the MongoDB helm chart. To change the username and password of the MongoDB instance, go ahead and [find the attribute where](https://github.com/kerberos-io/vault/blob/master/kubernetes/mongodb/values.yaml#L148) you can change the root password. Please note that we are using the official [Bitnami Mongodb helm chart](https://github.com/bitnami/charts/tree/main/bitnami/mongodb), so please use their repository for more indepth configuration.
@@ -299,16 +314,20 @@ With the Vault installed, we can proceed to configure the various components. Cu
![Configure Vault](./assets/images/configure-vault.gif)
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Storage Provider" button to complete the process. **_(!You are advised to generate more complex access and secret key for Minio, this is just for demo purposes, do not use this in production)_**
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. Verify and add the provider using the [Garage setup guide](./base/garage/README.md).
- Minio
- MinIO/S3-compatible provider type (Garage backend)
- Enabled: true
- Provider name: minio
- Provider name: garage
- Bucket name: mybucket
- Region: na
- Hostname: myminio-hl.minio-tenant:9000
- Access key: minio
- Secret key: minio123
- Region: garage
- Hostname: garage.garage:3900
- SSL: false
- Access key: `access-key` from Secret `garage/garage-credentials`
- Secret key: `secret-key` from the same Secret
For the optional legacy MinIO installation, retain that backend's hostname,
credentials and provider name instead.
- Navigate to the `Integrations` menu and select the (+ Add Integration) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Integration" button to complete the process. **_(!You are advised to generate more complex username and password for RabbitMQ, this is just for demo purposes, do not use this in production)_**
@@ -325,16 +344,16 @@ With the Vault installed, we can proceed to configure the various components. Cu
- Enabled: true
- Account name: myaccount
- Main provider: minio
- Main provider: garage (or the retained provider for an existing installation)
- Day limit: 30
- Integration: rabbitmq
- Directory: \*
- Access key: XJoi2@bgSOvOYBy# (or generate new keys, but don't forget to update them in the next steps)
- Secret key: OGGqat4lXRpL@9XBYc8FUaId@5 (or generate new keys, but don't forget to update them in the next steps)
### Create a Agent
### Create an Agent
After deploying the Vault and configuring the necessary services for storage, database, and integration, you can proceed to deploy the Agent with the appropriate configuration. Review the `kerberos-agent-deployment.yaml` file and adjust the relevant settings, such as the RTSP URL, to ensure proper functionality. Please note that you can allow opt for the [Kerberos Factory](https://github.com/kerberos-io/factory/tree/master/kubernetes) which gives you a UI to manage the creation of Agents. Also please note if you generated new the keys in the previous Vault account creation, you need to update those in the Agent deployment.
After deploying the Vault and configuring the necessary services for storage, database, and integration, you can proceed to deploy the Agent with the appropriate configuration. Review the `kerberos-agent-deployment.yaml` file and adjust the relevant settings, such as the RTSP URL, to ensure proper functionality. Please note that you can allow opt for the [Factory](https://github.com/kerberos-io/factory/tree/master/kubernetes) which gives you a UI to manage the creation of Agents. Also please note if you generated new the keys in the previous Vault account creation, you need to update those in the Agent deployment.
```bash
kubectl apply -f kerberos-agent-deployment.yaml
@@ -412,7 +431,7 @@ kubectl apply -f kerberos-hub-create-database-job.yaml -n kerberos-hub
## Cleanup
If you consider to remove the Kerberos.io stack you might just disable the microk8s installation
If you consider to remove the ecosystem you might just disable the microk8s installation
```bash
microk8s reset
+37
View File
@@ -0,0 +1,37 @@
{
"groups": [
{ "id": "edge", "label": "Private edge sites - outbound uploads", "x": 0, "y": 20, "w": 500, "h": 760 },
{ "id": "cloud", "label": "Cloud - managed Kubernetes and backing services", "x": 620, "y": 20, "w": 700, "h": 760 }
],
"nodes": [
{ "id": "camera-1", "kind": "camera", "x": 30, "y": 170, "w": 180, "h": 140,
"header": "SITE A", "title": "Cameras", "subtitle": "Private network" },
{ "id": "camera-2", "kind": "camera", "x": 30, "y": 530, "w": 180, "h": 140,
"header": "SITE B", "title": "Cameras", "subtitle": "Private network" },
{ "id": "agent-1", "kind": "agent", "x": 270, "y": 170, "w": 200, "h": 140,
"header": "RECORD", "title": "Agents", "subtitle": "One per camera" },
{ "id": "agent-2", "kind": "agent", "x": 270, "y": 530, "w": 200, "h": 140,
"header": "RECORD", "title": "Agents", "subtitle": "One per camera" },
{ "id": "vault", "kind": "vault", "x": 650, "y": 170, "w": 230, "h": 140,
"header": "STORE", "title": "Vault", "subtitle": "Receive recordings" },
{ "id": "storage", "kind": "storage", "x": 1040, "y": 170, "w": 250, "h": 140,
"header": "RECORDINGS", "title": "Object storage", "subtitle": "S3 / Azure Blob / GCS" },
{ "id": "broker", "kind": "amqp", "x": 650, "y": 380, "w": 230, "h": 140,
"header": "EVENTS", "title": "RabbitMQ", "subtitle": "Queue recording events" },
{ "id": "mongodb", "kind": "database", "x": 1040, "y": 380, "w": 250, "h": 140,
"header": "METADATA", "title": "MongoDB", "subtitle": "Vault / Hub databases" },
{ "id": "hub", "kind": "hub", "x": 650, "y": 600, "w": 230, "h": 140,
"header": "ANALYSE", "title": "Hub", "subtitle": "Pipeline, API and UI" }
],
"connections": [
{ "from": "camera-1", "to": "agent-1", "fromSide": "right", "toSide": "left" },
{ "from": "camera-2", "to": "agent-2", "fromSide": "right", "toSide": "left" },
{ "from": "agent-2", "to": "vault", "fromSide": "right", "toSide": "left" },
{ "from": "agent-1", "to": "vault", "fromSide": "right", "toSide": "left", "label": "HTTPS upload" },
{ "from": "vault", "to": "mongodb", "fromSide": "right", "toSide": "left" },
{ "from": "vault", "to": "storage", "fromSide": "right", "toSide": "left", "label": "Recordings" },
{ "from": "vault", "to": "broker", "fromSide": "bottom", "toSide": "top", "label": "Events" },
{ "from": "broker", "to": "hub", "fromSide": "bottom", "toSide": "top", "label": "Process" },
{ "from": "hub", "to": "mongodb", "fromSide": "right", "toSide": "bottom" }
]
}
@@ -0,0 +1,39 @@
{
"groups": [
{ "id": "edge", "label": "Private edge network - Docker / Kubernetes", "x": 0, "y": 20, "w": 1240, "h": 680 }
],
"nodes": [
{ "id": "camera-1", "kind": "camera", "x": 30, "y": 280, "w": 180, "h": 140,
"header": "CAMERA", "title": "Camera 1", "subtitle": "RTSP stream" },
{ "id": "camera-2", "kind": "camera", "x": 30, "y": 510, "w": 180, "h": 140,
"header": "CAMERA", "title": "Camera 2", "subtitle": "RTSP stream" },
{ "id": "factory", "kind": "factory", "x": 280, "y": 80, "w": 220, "h": 140,
"header": "OPTIONAL ORCHESTRATION", "title": "Factory", "subtitle": "Manage on Kubernetes" },
{ "id": "agent-1", "kind": "agent", "x": 280, "y": 280, "w": 220, "h": 140,
"header": "RECORD", "title": "Agent 1", "subtitle": "One camera per container" },
{ "id": "agent-2", "kind": "agent", "x": 280, "y": 510, "w": 220, "h": 140,
"header": "RECORD", "title": "Agent 2", "subtitle": "One camera per container" },
{ "id": "vault", "kind": "vault", "x": 620, "y": 280, "w": 230, "h": 140,
"header": "STORE", "title": "Vault", "subtitle": "Centralise edge recordings" },
{ "id": "mongodb", "kind": "database", "x": 970, "y": 80, "w": 240, "h": 140,
"header": "METADATA", "title": "MongoDB", "subtitle": "Vault metadata" },
{ "id": "storage", "kind": "storage", "x": 970, "y": 280, "w": 240, "h": 140,
"header": "RECORDINGS", "title": "Object storage", "subtitle": "MinIO / S3-compatible" },
{ "id": "broker", "kind": "amqp", "x": 970, "y": 510, "w": 240, "h": 140,
"header": "OPTIONAL EVENTS", "title": "RabbitMQ", "subtitle": "Recording events" },
{ "id": "integration", "kind": "default", "x": 620, "y": 510, "w": 230, "h": 140,
"header": "OPTIONAL INTEGRATION", "title": "Custom application", "subtitle": "Filter, notify or integrate" }
],
"connections": [
{ "from": "camera-1", "to": "agent-1", "fromSide": "right", "toSide": "left", "label": "RTSP" },
{ "from": "camera-2", "to": "agent-2", "fromSide": "right", "toSide": "left", "label": "RTSP" },
{ "from": "factory", "to": "agent-1", "fromSide": "bottom", "toSide": "top", "kind": "dashed" },
{ "from": "factory", "to": "agent-2", "fromSide": "left", "toSide": "left", "kind": "dashed" },
{ "from": "agent-2", "to": "vault", "fromSide": "right", "toSide": "left" },
{ "from": "agent-1", "to": "vault", "fromSide": "right", "toSide": "left", "label": "Upload" },
{ "from": "vault", "to": "mongodb", "fromSide": "top", "toSide": "left" },
{ "from": "vault", "to": "storage", "fromSide": "right", "toSide": "left" },
{ "from": "vault", "to": "broker", "fromSide": "right", "toSide": "left", "label": "Events" },
{ "from": "broker", "to": "integration", "fromSide": "left", "toSide": "right", "label": "Consume" }
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"groups": [
{ "id": "capture", "label": "1. Camera processing - edge / cloud", "x": 0, "y": 20, "w": 540, "h": 520 },
{ "id": "persistence", "label": "2. Persistence - edge / cloud", "x": 620, "y": 20, "w": 300, "h": 520 },
{ "id": "analysis", "label": "3. Analyse - edge / cloud", "x": 1000, "y": 20, "w": 300, "h": 520 }
],
"nodes": [
{ "id": "camera-1", "kind": "camera", "x": 30, "y": 230, "w": 180, "h": 140,
"header": "CAMERA", "title": "Camera 1", "subtitle": "RTSP stream" },
{ "id": "camera-2", "kind": "camera", "x": 30, "y": 390, "w": 180, "h": 140,
"header": "CAMERA", "title": "Camera 2", "subtitle": "RTSP stream" },
{ "id": "factory", "kind": "factory", "x": 280, "y": 60, "w": 230, "h": 140,
"header": "ORCHESTRATE", "title": "Factory", "subtitle": "Manage on Kubernetes" },
{ "id": "agent-1", "kind": "agent", "x": 280, "y": 230, "w": 230, "h": 140,
"header": "RECORD", "title": "Agent 1", "subtitle": "Process one camera" },
{ "id": "agent-2", "kind": "agent", "x": 280, "y": 390, "w": 230, "h": 140,
"header": "RECORD", "title": "Agent 2", "subtitle": "Process one camera" },
{ "id": "vault", "kind": "vault", "x": 650, "y": 230, "w": 240, "h": 140,
"header": "STORE", "title": "Vault", "subtitle": "Store and integrate" },
{ "id": "storage", "kind": "storage", "x": 650, "y": 390, "w": 240, "h": 140,
"header": "OBJECT STORAGE", "title": "Storage backend", "subtitle": "Cloud / edge" },
{ "id": "hub", "kind": "hub", "x": 1030, "y": 230, "w": 240, "h": 140,
"header": "ANALYSE", "title": "Hub", "subtitle": "Monitor and analyse" }
],
"connections": [
{ "from": "camera-1", "to": "agent-1", "fromSide": "right", "toSide": "left" },
{ "from": "camera-2", "to": "agent-2", "fromSide": "right", "toSide": "left" },
{ "from": "factory", "to": "agent-1", "fromSide": "bottom", "toSide": "top", "kind": "dashed" },
{ "from": "factory", "to": "agent-2", "fromSide": "right", "toSide": "right", "kind": "dashed" },
{ "from": "agent-1", "to": "vault", "fromSide": "right", "toSide": "left" },
{ "from": "agent-2", "to": "vault", "fromSide": "right", "toSide": "left" },
{ "from": "vault", "to": "storage", "fromSide": "bottom", "toSide": "top" },
{ "from": "vault", "to": "hub", "fromSide": "right", "toSide": "left", "label": "Events" }
]
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 994 KiB

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 694 KiB

After

Width:  |  Height:  |  Size: 95 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 83 KiB

+183
View File
@@ -0,0 +1,183 @@
# Factory — base manifests
This directory holds the raw Kubernetes manifests for **Factory**. Factory
is a web application that runs *inside* your cluster and uses the Kubernetes API to
deploy, configure and observe Agents (one Deployment + Service per camera).
> **Scope of this README.** At the top level of this repository Factory is normally
> installed through **Kustomize** (see [`overlays/`](../../overlays) and the
> [`README.kustomize.md`](../../README.kustomize.md)). This README documents the
> alternative: applying the manifests in this folder **directly with `kubectl`**,
> without Kustomize. Use it when you want to install only Factory, understand each
> object in isolation, or integrate these manifests into your own tooling.
## What gets deployed
| File | Kind | Purpose |
| ---- | ---- | ------- |
| [`kerberos-factory-deployment.yaml`](./kerberos-factory-deployment.yaml) | `Deployment` | Hardened `ghcr.io/uug-ai/factory:v2.0.13`, pinned to its verified multi-architecture digest, container port `8080`. |
| [`kerberos-factory-service.yaml`](./kerberos-factory-service.yaml) | `Service` | Exposes Factory on `NodePort` **30079** (a `LoadBalancer` variant is included, commented out). |
| [`kerberos-factory-clusterrole.yaml`](./kerberos-factory-clusterrole.yaml) | `ClusterRole` + `ClusterRoleBinding` | Grants the `default` ServiceAccount in `kerberos-factory` the API access Factory needs to manage Agents. |
The matching namespace (`kerberos-factory`) is defined one level up in
[`../namespaces/kerberos-factory.yaml`](../namespaces/kerberos-factory.yaml).
## Configuration (ConfigMap store + Kubernetes engine)
These manifests are configured to run Factory **without MongoDB**. Factory keeps its
own (global/template) configuration and delivers each Agent's configuration through
Kubernetes **ConfigMaps**, and schedules Agents with the **Kubernetes** engine. This
is controlled by two environment variables on the Deployment:
```yaml
- name: FACTORY_CONFIGURATION
value: "configmap" # store config in ConfigMaps (json | configmap | secret | mongodb)
- name: FACTORY_ENGINE
value: "kubernetes" # schedule agents as Deployments (kubernetes | docker | host)
```
On start-up Factory bootstraps two cluster ConfigMaps — `agent-global-config`
(settings every Agent inherits) and `agent-template-config` (the base used for new
Agents) — and creates a per-agent `<name>-config` ConfigMap for each Agent it
provisions. **This is why the ClusterRole includes `configmaps`** — without that
permission the bootstrap fails.
Other relevant environment variables:
| Variable | Default here | Meaning |
| -------- | ------------ | ------- |
| `KERBEROS_LOGIN_USERNAME` / `KERBEROS_LOGIN_PASSWORD` | `root` / `kerberos` | Factory UI login. **Change these for anything but a demo.** |
| `KERBEROS_AGENT_IMAGE` | `kerberos/agent:latest` | Image used when Factory creates an Agent. |
| `KERBEROS_AGENT_MEMORY_LIMIT` | `256Mi` | Default memory limit for created Agents. |
| `NAMESPACE` | `kerberos-factory` | Namespace Factory schedules Agents into. |
| `PORT` | `8080` | Unprivileged HTTP listener used by the hardened image. |
| `K8S_PROXY` | `http://localhost:8080` | Internal proxy address Factory calls for the `/kubernetes` API. |
## Hardened runtime and upgrades
The image runs as UID/GID `65532:65532`, with a read-only root filesystem,
all Linux capabilities dropped, privilege escalation disabled and the
`RuntimeDefault` seccomp profile. The UI login named `root` is independent
of the container's operating-system user.
Two disk-backed `emptyDir` volumes are writable:
- `/home/factory/config` receives missing seed configuration files.
- `/tmp` is scratch space, including multipart uploads.
The pod's `fsGroup: 65532` makes these volumes writable without a root
init container. They survive a container restart but are replaced with the
pod. In this base's `configmap` mode, the global configuration is restored
from the existing Kubernetes ConfigMap on startup; existing global/template
ConfigMaps are not overwritten by image defaults. Keep the existing
ServiceAccount token and RBAC: Factory manages Agents through the Kubernetes
API even though its process is non-root.
Apply the Deployment and Service together when upgrading from a legacy
port-80 image. The external Service port `80` and NodePort `30079` are unchanged;
the Service now targets the named `http` port on container port `8080`.
Startup, readiness and liveness probes use `/healthz`; this endpoint checks
the HTTP process, not all downstream Kubernetes dependencies.
Back up any legacy configuration stored only in the old container before
replacing its pod. For `FACTORY_CONFIGURATION=json`, replace the config
`emptyDir` with a writable PVC and preserve its existing JSON files before
upgrading. A PVC must support access by UID/GID `65532`; storage backends that
do not honor `fsGroup` need matching permissions provisioned separately.
CI upgrades the last legacy release (`v2.0.11`) to these actual base manifests
on kind. It checks the restricted runtime, Service routing, UI/login,
ConfigMap bootstrap, saving settings and persistence after pod replacement.
The [smoke test](../../scripts/check-factory.sh) changes settings and restarts
Factory, so it refuses non-CI invocation and is intended only for disposable
test clusters.
## Prerequisites
- A running Kubernetes cluster and a `kubectl` configured to reach it.
- Permission to create `ClusterRole`/`ClusterRoleBinding` (cluster-admin or equivalent).
## Deploy with `kubectl` (without Kustomize)
The manifest files do **not** hard-code a namespace (Kustomize injects it at the upper
level). When applying directly, target the namespace explicitly with `-n`.
```bash
# 1. Create the namespace (only needed if it does not exist yet)
kubectl apply -f ../namespaces/kerberos-factory.yaml
# 2. Create the RBAC. The ClusterRole/ClusterRoleBinding are cluster-scoped;
# the binding's subject already references the kerberos-factory namespace.
kubectl apply -f ./kerberos-factory-clusterrole.yaml
# 3. Deploy Factory and its service into the namespace
kubectl apply -n kerberos-factory -f ./kerberos-factory-deployment.yaml
kubectl apply -n kerberos-factory -f ./kerberos-factory-service.yaml
```
Or apply the whole folder at once (RBAC is cluster-scoped, the rest lands in the
namespace):
```bash
kubectl apply -f ../namespaces/kerberos-factory.yaml
kubectl apply -n kerberos-factory -f ./
```
Verify the rollout:
```bash
kubectl get pods,svc -n kerberos-factory
kubectl rollout status deployment/factory -n kerberos-factory
kubectl logs -n kerberos-factory deploy/factory
```
You should see log lines confirming the global and template Agent ConfigMaps were
bootstrapped.
## Access the UI
With the `NodePort` service, Factory is reachable on port **30079** of any node:
```bash
# Example: open http://<node-ip>:30079
kubectl get nodes -o wide # find a node IP
# Or port-forward without exposing a node port
kubectl port-forward -n kerberos-factory svc/factory-nodeport 8080:80
# then browse http://localhost:8080
```
Log in with the `KERBEROS_LOGIN_USERNAME` / `KERBEROS_LOGIN_PASSWORD` values above
(default `root` / `kerberos`).
To use a cloud `LoadBalancer` instead of a `NodePort`, uncomment the `factory-lb`
service at the bottom of [`kerberos-factory-service.yaml`](./kerberos-factory-service.yaml)
and comment out the `NodePort` service.
## Switching the configuration store
ConfigMap storage is recommended for a clean, database-free install, but Factory
supports other stores via `FACTORY_CONFIGURATION`:
- `configmap` *(default here)* — config in ConfigMaps, no MongoDB.
- `secret` — same as `configmap` but sensitive values are kept in Kubernetes Secrets
(add `secrets` to the ClusterRole resources for this mode).
- `json` — config in local JSON files, no MongoDB. Use a PVC at
`/home/factory/config`; the default `emptyDir` is not durable across pods.
- `mongodb` — legacy behaviour: Factory and Agents read config from MongoDB. For this
you also need a reachable MongoDB and the corresponding `MONGODB_*` environment
variables (e.g. via a `mongodb` ConfigMap mounted with `envFrom`).
See the [Configuration & engines documentation](https://github.com/uug-ai/factory)
for the full model.
## Uninstall
```bash
kubectl delete -n kerberos-factory -f ./kerberos-factory-service.yaml
kubectl delete -n kerberos-factory -f ./kerberos-factory-deployment.yaml
kubectl delete -f ./kerberos-factory-clusterrole.yaml
# Optionally remove the agent ConfigMaps Factory created and the namespace
kubectl delete configmap -n kerberos-factory agent-global-config agent-template-config --ignore-not-found
kubectl delete -f ../namespaces/kerberos-factory.yaml
```
@@ -4,7 +4,7 @@ metadata:
name: pods-list
rules:
- apiGroups: ["", "apps"]
resources: ["pods", "pods/log", "deployments", "services", "services/proxy", "endpoints", "nodes"]
resources: ["pods", "pods/log", "deployments", "services", "services/proxy", "endpoints", "nodes", "configmaps"]
verbs: ["get", "list", "create", "update", "delete", "watch"]
---
kind: ClusterRoleBinding
+54 -11
View File
@@ -18,13 +18,21 @@ spec:
labels:
app: factory
spec:
initContainers:
- name: wait-for-mongodb-before-starup
image: busybox
command: ["sh", "-c", "until nc -z mongodb.mongodb 27017 > /dev/null; do echo Waiting for master.; sleep 2; done;"]
securityContext:
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: factory
image: "uugai/factory:v1.0.5"
image: ghcr.io/uug-ai/factory:v2.0.13@sha256:ade073660d911ca66d75b2abc298f1ddcbfbfe43aeed44e60fc1984b49b73d95
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
resources:
requests:
memory: 128Mi
@@ -33,11 +41,36 @@ spec:
memory: 128Mi
cpu: 100m
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: mongodb
- name: http
containerPort: 8080
protocol: TCP
startupProbe:
httpGet:
path: /healthz
port: http
failureThreshold: 30
periodSeconds: 2
timeoutSeconds: 2
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 10
timeoutSeconds: 2
volumeMounts:
- name: factory-config
mountPath: /home/factory/config
- name: factory-tmp
mountPath: /tmp
env:
- name: PORT
value: "8080"
- name: GIN_MODE
value: release
- name: KERBEROS_LOGIN_USERNAME
@@ -53,8 +86,18 @@ spec:
# Do not touch this, unless you know what you are doing.
- name: NAMESPACE
value: "kerberos-factory"
# Store the factory and agent configuration in Kubernetes ConfigMaps
# (no MongoDB required) and schedule agents with the Kubernetes engine.
- name: FACTORY_CONFIGURATION
value: "configmap"
- name: FACTORY_ENGINE
value: "kubernetes"
- name: FACTORY_ENVIRONMENT
value: "kubernetes"
- name: K8S_PROXY
value: http://localhost:80
value: http://localhost:8080
volumes:
- name: factory-config
emptyDir: {}
- name: factory-tmp
emptyDir: {}
+2 -2
View File
@@ -10,7 +10,7 @@ spec:
type: NodePort
ports:
- port: 80
targetPort: 80
targetPort: http
nodePort: 30079 # You can specify a port in the range 30000-32767 or let Kubernetes assign one automatically
name: frontend
protocol: TCP
@@ -27,7 +27,7 @@ spec:
# type: LoadBalancer
# ports:
# - port: 80
# targetPort: 80
# targetPort: http
# name: frontend
# protocol: TCP
# selector:
-1
View File
@@ -1,6 +1,5 @@
namespace: kerberos-factory
resources:
- mongodb-configmap.yaml
- kerberos-factory-deployment.yaml
- kerberos-factory-service.yaml
- kerberos-factory-clusterrole.yaml
-18
View File
@@ -1,18 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mongodb
data:
# This is the mongodb database where data will be stored, you might use a different name if you want.
MONGODB_DATABASE_STORAGE: "KerberosStorage"
MONGODB_DATABASE_FACTORY: "KerberosFactory"
MONGODB_DATABASE_HUB: "Kerberos"
# MongoDB URI (for example for a SaaS service like MongoDB Atlas)
# If uri is set, the below properties are not used (host, adminDatabase, username, password)
#MONGODB_URI: "mongodb+srv://xx:xx@kerberos-hub.xxx.mongodb.net/?retryWrites=true&w=majority&appName=xxx"
# If you do not wish to use the URI, you can specify the individual values.
MONGODB_HOST: "mongodb.mongodb"
MONGODB_DATABASE_CREDENTIALS: "admin"
MONGODB_USERNAME: "root"
MONGODB_PASSWORD: "yourpassword"
+105
View File
@@ -0,0 +1,105 @@
# Garage S3 for local Kubernetes deployments
The default Kubernetes stack uses [Garage](https://garagehq.deuxfleurs.fr/),
not the MinIO operator. The pinned multi-architecture `dxflrs/garage:v2.5.0`
image runs as a single non-root node with SQLite metadata and object data on
the same 10Gi `ssd-hostpath` PVC. This is a local/edge/CI example with **no
replication or high availability**, not a production multi-node layout.
Use Garage's [production deployment guide](https://garagehq.deuxfleurs.fr/documentation/cookbook/real-world/)
for replicated storage. Choose capacity and a backup policy before storing
recordings; resizing a bound PVC depends on your storage provisioner.
## Install
First install your storage provisioner and create the `ssd-hostpath`
StorageClass (the same prerequisite used by MongoDB and RabbitMQ). Run commands
from the deployment repository root:
```sh
bash scripts/install-garage.sh
bash scripts/check-garage.sh
```
The installer generates an RPC secret and S3 credentials in the Kubernetes
Secret `garage/garage-credentials`, and reuses them on subsequent runs. No
credentials are committed to the repository. Garage's `--single-node` and
`--default-bucket` startup flags initialize the layout and `mybucket`.
The installer refuses to generate new credentials if the PVC already exists
but its Secret is missing: restore the original Secret instead.
The installer requires `kubectl`, `jq` and `openssl`. The smoke test also
requires AWS CLI and `curl` (provided by the GitHub-hosted CI runners).
For a full Kustomize installation, prepare credentials before applying the
overlay; the default base already includes Garage:
```sh
bash scripts/install-garage.sh --prepare-only
kubectl kustomize overlays/microk8s --enable-helm --load-restrictor LoadRestrictionsNone | kubectl apply -f -
kubectl -n garage rollout status statefulset/garage --timeout=300s
```
The `configure.sh apply` helper also prepares the Secret before applying its
overlay. Back up the Secret and the PVC together. Do not delete the namespace,
Secret or PVC as an upgrade/recovery step.
## Configure Vault
Add a storage provider using Vault's existing **MinIO/S3-compatible provider
type**. The provider *name* identifies the instance and is distinct from its
implementation type:
| Setting | Value |
| --- | --- |
| Provider type | MinIO (`minio`) |
| Provider name | `garage` |
| Bucket | `mybucket` |
| Region | `garage` |
| Internal hostname | `garage.garage:3900` (no URL scheme) |
| SSL | Disabled for this local HTTP example |
| Access key | Secret field `access-key` |
| Secret key | Secret field `secret-key` |
Retrieve credentials locally when configuring Vault; do not paste them into
logs, issues, or committed values:
```sh
kubectl -n garage get secret garage-credentials -o jsonpath='{.data.access-key}' | base64 --decode
kubectl -n garage get secret garage-credentials -o jsonpath='{.data.secret-key}' | base64 --decode
```
The S3 endpoint is exposed at `http://NODE_IP:30090`. If browsers or remote
clients consume provider-native presigned URLs, configure Vault's external
hostname as `NODE_IP:30090` with external SSL disabled (or a real TLS endpoint).
Cluster-only DNS names cannot be resolved by external clients. Protect exposed
ports with firewall rules and use TLS outside a trusted local network. Garage's
RPC port is bound to loopback; no admin API or web console is exposed.
Set the Vault account's main provider to `garage`. The supplied Hub values
also use that name for recording, archive, thumbnail and sprite access.
**Vault account credentials are separate from Garage S3 credentials**: keep
the existing Agent/Hub account credentials aligned with the Vault account,
not with the generated S3 key.
## Verification
`scripts/check-garage.sh` uses AWS CLI for an authenticated Signature V4 PUT/GET
through the Kubernetes Service and compares object bytes. It retains a small,
uniquely named object under `mybucket/ci-smoke/`; it never deletes existing
objects. The optional `--restart` flag restarts the Garage pod between upload
and download to verify persistence; use it only in CI or a maintenance window.
CI also verifies that reinstalling Garage preserves its credential Secret.
## Existing MinIO installations
The [MinIO manifests](../minio/) remain available as an explicit legacy option.
This change does **not** copy recordings, modify Vault's stored provider
records, or delete MinIO resources. Do not use prune/apply cleanup to remove
the old backend while it contains recordings.
When upgrading an existing deployment, keep its existing Vault provider names
in the Hub values (`kerberosvault.provider`, `archive.provider`,
`thumbnail.provider`, `sprite.provider`) until recordings have been explicitly
migrated and reads verified. Add Garage as a separate provider/account as
needed. Treat object migration and retirement of MinIO as a separate,
backed-up operation; this installer only provisions the new backend.
+128
View File
@@ -0,0 +1,128 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: garage
data:
garage.toml: |
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1
rpc_bind_addr = "127.0.0.1:3901"
rpc_public_addr = "127.0.0.1:3901"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
---
apiVersion: v1
kind: Service
metadata:
name: garage
spec:
type: NodePort
selector:
app: garage
ports:
- name: s3
port: 3900
targetPort: s3
nodePort: 30090
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: garage
spec:
serviceName: garage
replicas: 1
selector:
matchLabels:
app: garage
template:
metadata:
labels:
app: garage
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
containers:
- name: garage
image: dxflrs/garage:v2.5.0@sha256:677e5b758e4425a325b811c425c026bf35a4bf42f7ddbaa773de3f4fd779a5a5
command: ["/garage"]
args: ["server", "--single-node", "--default-bucket"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
env:
- name: GARAGE_RPC_SECRET
valueFrom:
secretKeyRef:
name: garage-credentials
key: rpc-secret
- name: GARAGE_DEFAULT_ACCESS_KEY
valueFrom:
secretKeyRef:
name: garage-credentials
key: access-key
- name: GARAGE_DEFAULT_SECRET_KEY
valueFrom:
secretKeyRef:
name: garage-credentials
key: secret-key
- name: GARAGE_DEFAULT_BUCKET
value: mybucket
ports:
- name: s3
containerPort: 3900
startupProbe:
tcpSocket:
port: s3
periodSeconds: 2
failureThreshold: 60
readinessProbe:
exec:
command: ["/garage", "health"]
livenessProbe:
tcpSocket:
port: s3
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
volumeMounts:
- name: config
mountPath: /etc/garage.toml
subPath: garage.toml
readOnly: true
- name: data
mountPath: /var/lib/garage
- name: tmp
mountPath: /tmp
volumes:
- name: config
configMap:
name: garage
- name: tmp
emptyDir:
sizeLimit: 16Mi
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ssd-hostpath
resources:
requests:
storage: 10Gi
+4
View File
@@ -0,0 +1,4 @@
namespace: garage
resources:
- namespace.yaml
- garage.yaml
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: garage
+220 -9
View File
@@ -43,14 +43,71 @@ data:
conn = db.getMongo();
db = conn.getDB('Kerberos');
const ownerUsername = 'example-user';
const applicationUsername = 'example-application';
const organisationName = 'example-orgranisation';
const zeroObjectId = ObjectId('000000000000000000000000');
const ownerCreatedAt = ISODate('2016-09-20T09:27:58.811Z');
const ownerUpdatedAt = ISODate('2020-06-14T05:01:35.000Z');
function resolveUserId(username) {
const users = db.users.find(
{ username: username },
{ _id: 1 }
).limit(2).toArray();
if (users.length > 1) {
throw new Error(`Cannot import duplicate username: ${username}`);
}
if (users.length === 1) {
if (!users[0]._id || typeof users[0]._id.toHexString !== 'function') {
throw new Error(`Existing user ${username} does not have an ObjectId _id`);
}
return users[0]._id;
}
return ObjectId();
}
const ownerId = resolveUserId(ownerUsername);
const applicationId = resolveUserId(applicationUsername);
function assertAcknowledged(result, operation) {
if (!result.acknowledged) {
throw new Error(`${operation} was not acknowledged`);
}
}
function insertIfMissing(collection, id, values) {
const result = db.getCollection(collection).updateOne(
{ _id: id },
{ $setOnInsert: values },
{ upsert: true }
);
assertAcknowledged(result, `Insert for ${collection}/${id}`);
}
function setObjectIdIfMissing(collection, id, field, value) {
const result = db.getCollection(collection).updateOne(
{
_id: id,
$or: [
{ [field]: { $exists: false } },
{ [field]: null },
{ [field]: zeroObjectId }
]
},
{ $set: { [field]: value } }
);
assertAcknowledged(result, `Initialisation of ${collection}.${field}`);
}
// This will create a user with the following credentials.
// username: example-user
// password: example-password
db.users.updateOne(
{ '_id': ObjectId('57e1011e3178aa6c5cc774d1') },
{ '_id': ownerId },
{
$set: {
'username': 'example-user',
'username': ownerUsername,
'email': 'example-user@email.com',
'password': '$2a$10$jwLcD/.UT/1WLK7ct1XuHewI3GQXwW3zerPhCCs7QDrReEuIHbVYi',
'role': 'owner',
@@ -58,8 +115,8 @@ data:
'timezone': 'Europe/Brussels',
'isActive': NumberLong(1),
'registerToken': '',
'updated_at': ISODate('2020-06-14T05:01:35.000Z'),
'created_at': ISODate('2016-09-20T09:27:58.811Z'),
'updated_at': ownerUpdatedAt,
'created_at': ownerCreatedAt,
'amazon_secret_access_key': 'K6rRLBI1xxxCk3C1H',
'amazon_access_key_id': 'AKIAxxxxxxG5Q',
'card_brand': 'Visa',
@@ -71,16 +128,85 @@ data:
{ upsert: true }
);
// The owner gets a random ObjectId on first import and is resolved by
// username on reruns. Its organisation and default project share it.
insertIfMissing('organisation', ownerId, {
name: organisationName,
ownerId: ownerId,
isActive: true,
settings: {
timezone: 'Europe/Brussels'
},
audit: {
createdBy: ownerId.toHexString(),
createdAt: ownerCreatedAt,
updatedBy: ownerId.toHexString(),
updatedAt: ownerUpdatedAt,
lastAction: 'organisation.created'
}
});
const membershipResult = db.organisation_users.updateOne(
{
userId: ownerId,
organisationId: ownerId
},
{
$setOnInsert: {
userId: ownerId,
organisationId: ownerId,
status: 'active',
invitedBy: ownerId,
joinedAt: ownerCreatedAt,
audit: {
createdBy: ownerId.toHexString(),
createdAt: ownerCreatedAt,
updatedBy: ownerId.toHexString(),
updatedAt: ownerUpdatedAt,
lastAction: 'organisation.membership.created'
}
}
},
{ upsert: true }
);
assertAcknowledged(membershipResult, 'Owner organisation membership');
insertIfMissing('project', ownerId, {
organisationId: ownerId,
name: 'Project 1',
slug: 'project-1',
isActive: true,
audit: {
createdBy: ownerId.toHexString(),
createdAt: ownerCreatedAt,
updatedBy: ownerId.toHexString(),
updatedAt: ownerUpdatedAt,
lastAction: 'project.default.created'
}
});
// Initialise only missing selections. Re-running the import must not
// reset a user who has since selected another organisation or project.
setObjectIdIfMissing('users', ownerId, 'organisationId', ownerId);
const importedOwner = db.users.findOne(
{ _id: ownerId, organisationId: { $type: 'objectId' } },
{ organisationId: 1 }
);
if (!importedOwner) {
throw new Error('Imported owner has an invalid organisationId');
}
setObjectIdIfMissing('users', ownerId, 'projectId', importedOwner.organisationId);
// This will create an application with the following credentials, which
// can be used to authenticate with the admin panel.
// username: example-application
// password: example-password
db.users.updateOne(
{ '_id': ObjectId('57e1011e3178aa6c5cc774d2') },
{ '_id': applicationId },
{
$set: {
'username': 'example-application',
'username': applicationUsername,
'email': 'example-application@email.com',
'password': '$2a$10$jwLcD/.UT/1WLK7ct1XuHewI3GQXwW3zerPhCCs7QDrReEuIHbVYi',
'role': 'application',
@@ -99,7 +225,7 @@ data:
// Subscriptions allow you to modify specific permissions and features for a user.
// As you'll see below you can specify specific threshold for each subscription.
db.subscriptions.updateOne(
{ '_id': ObjectId('57e1011e3178aa6c5cc774d1') },
{ '_id': ownerId },
{
$set: {
'name': 'default',
@@ -108,7 +234,8 @@ data:
'quantity': 1,
'trial_ends_at': null,
'ends_at': null,
'user_id': '57e1011e3178aa6c5cc774d1',
'user_id': ownerId.toHexString(),
'status': 'active',
'updated_at': ISODate('2021-04-27T09:45:30.169Z'),
'created_at': ISODate('2016-09-20T09:35:03.448Z'),
'stripe_status': 'active'
@@ -116,6 +243,7 @@ data:
},
{ upsert: true }
);
setObjectIdIfMissing('subscriptions', ownerId, 'organisation_id', ownerId);
// This will create a settings object for the subscription.
// and will allow you to modify the settings for the subscription.
@@ -240,4 +368,87 @@ data:
}
},
{ upsert: true }
);
);
const importedUsers = db.users.countDocuments({
_id: {
$in: [ownerId, applicationId]
}
});
const importedOwnerUsernames = db.users.countDocuments({ username: ownerUsername });
const importedApplicationUsernames = db.users.countDocuments({ username: applicationUsername });
const importedOwnerSelection = db.users.findOne(
{
_id: ownerId,
organisationId: { $type: 'objectId' },
projectId: { $type: 'objectId' }
},
{ organisationId: 1, projectId: 1 }
);
const importedOrganisations = db.organisation.countDocuments({
_id: ownerId,
ownerId: ownerId,
isActive: true
});
const importedMemberships = db.organisation_users.countDocuments({
userId: ownerId,
organisationId: ownerId,
status: 'active'
});
const importedProjects = db.project.countDocuments({
_id: ownerId,
organisationId: ownerId,
slug: 'project-1',
isActive: true
});
const importedSubscriptions = db.subscriptions.countDocuments({
_id: ownerId,
organisation_id: ownerId
});
const importedSettings = db.settings.countDocuments({
_id: {
$in: [
ObjectId('5a72c509e17699d18ada9154'),
ObjectId('63f346ec64011a574161cf99'),
ObjectId('5a43fa12d885eb7da57046b3'),
ObjectId('5a4d3a6bd885eb7da5e6b297'),
ObjectId('5a53d0a0d885eb7da53ed5a6')
]
}
});
if (!importedOwnerSelection) {
throw new Error('Imported owner is missing ObjectId organisation/project selections');
}
const selectedOrganisationExists = db.organisation.countDocuments({
_id: importedOwnerSelection.organisationId
});
const selectedProjectExists = db.project.countDocuments({
_id: importedOwnerSelection.projectId,
organisationId: importedOwnerSelection.organisationId
});
const selectedProjectIsLogicalDefault = importedOwnerSelection.projectId.equals(
importedOwnerSelection.organisationId
);
if (
importedUsers !== 2 ||
importedOwnerUsernames !== 1 ||
importedApplicationUsernames !== 1 ||
importedOrganisations !== 1 ||
importedMemberships !== 1 ||
importedProjects !== 1 ||
importedSubscriptions !== 1 ||
importedSettings !== 5 ||
selectedOrganisationExists !== 1 ||
(!selectedProjectIsLogicalDefault && selectedProjectExists !== 1)
) {
throw new Error(
`Import verification failed: users=${importedUsers}, ` +
`organisations=${importedOrganisations}, memberships=${importedMemberships}, ` +
`projects=${importedProjects}, subscriptions=${importedSubscriptions}, ` +
`settings=${importedSettings}`
);
}
print('Imported 2 users, 1 organisation, 1 membership, 1 project, 1 subscription, and 5 settings records into Kerberos.');
+1 -1
View File
@@ -9,7 +9,7 @@ spec:
type: NodePort
ports:
- port: 80
targetPort: 80
targetPort: http
nodePort: 32080 # You can specify a port in the range 30000-32767 or let Kubernetes assign one automatically
name: frontend
protocol: TCP
+67 -58
View File
@@ -3,7 +3,7 @@
# Declare variables to be passed into your templates.
###########################################################################
# LICENSING information: you will a license to run Kerberos Hub.
# LICENSING information: you will a license to run Hub.
# ----
# Get your license from support@kerberos.io
#license: "---ENTER-YOUR-LICENSE-HERE---"
@@ -11,7 +11,7 @@
license: "L/+DAwEBB2xpY2Vuc2UB/4QAAQIBB1BheWxvYWQBCgABCVNpZ25hdHVyZQEKAAAA/gMk/4QB/gEZ/8sQACxnaXRodWIuY29tL3V1Zy1haS9odWItbGljZW5zZS9tb2RlbHMuTGljZW5zZX8DAQEHTGljZW5zZQH/gAABDAECSWQB/4IAAQNLZXkBDAABB0NvbXBhbnkBDAABB0V4cFRpbWUBBAABBERheXMBBAABB0NhbWVyYXMBBAABBVNpdGVzAQQAAQZWYXVsdHMBBAABCk1lZGlhTGltaXQBBAABCVBlcnBldHVhbAECAAEGQWN0aXZlAQIAAQlJcEFkZHJlc3MBDAAAABj/gQEBAQhPYmplY3RJRAH/ggABBgEYAAAy/4AvAQwAAAAAAAAAAAAAAAACDGZyZWUtbGljZW5zZQH81iZi6gH+AtoBEAEUARQDAQAB/gIAfeXxQb6kaPfAgOWeSAE6qEiQviFD6sciNmNfMel1mEL53FeV0GQe4cYBip9wyJag35az8A1yppxSymZD5V4my2FckyN2zmEW4E2sO/v+8eKepiAGYEzrKtfNCLxdWLmrHd0zjYQ3qk+PNfoPyzCOefeulw3aFsqBlzg9wDkF8cRx6tUW0qNTzki6sFGOuLoxS49cWqsftAvZmt+CRWa8u0VArIAjOpywN0RIZCkEYzp5RYF3LSVyWYEyvVhjE19DDnevzpJyCHRsIHTRcpTQkhboeapOdlEz8cx+PaOvxktN8hBWceTAH+nw96FARG7y6Cpjw3xo+NV1xb0tvRXGaGoK77JnErKLhd/haXji98rGvMakDt18WSbQfTVS84+Fw+/gKGsW3uS3fROAaZw1kZj4PgsEPZDvbVkCVyuS0O87UoYqpxH8S4by8cTF3wDP7FwyIRZbEbYjN2wzHSmlADYOBtdsdb1VGm7wvtB85vML9n7ZSlIpJdqfci06mGks102mDyG2LRMhUEvpUW3D/weErIvj2WiAwf6r0EUj+LO8VmAsYkME9da7FXEN6Vg/5f1u485LOpYki332RaDOhDn2eMG9DVb/HnmQSFagX+XXc/QwfsWiehCgKYGk4jQpyklTqoGu8BAt6Sm8CaoH8ngZ3cHLQT5DcZElV36/N/wA"
licenseServer:
url: "https://license.kerberos.io/verify"
token: "214%ˆ#ddfsf@#3rfdsgl_)23sffeqasSwefDSFNBM" # do not change otherwise Kerberos Hub will not work.
token: "214%ˆ#ddfsf@#3rfdsgl_)23sffeqasSwefDSFNBM" # do not change otherwise Hub will not work.
# Private Docker Registry: The registry secret is required if you have your Docker images behind a private registry.
# By default it will pull from Docker hub (https://hub.docker.com/r/kerberos).
#imagePullSecrets:
@@ -19,14 +19,14 @@ licenseServer:
# When using a private registry, you can specify the registry here.
global:
imageRegistry: "docker.io/"
imageRegistry: ""
# Environment: set to 'production', 'develop', 'demo', 'staging' or 'acceptance'.
# Set to 'true' if this is a private deployment.
environment: "production"
# Set to 'true' if this is a private deployment.
isPrivate: true
# If you plan a migration or doing maintenance, you can enable readonly.
# This will stop any write process to mongodb or any processing done in the Kerberos Hub pipeline.
# This will stop any write process to mongodb or any processing done in the Hub pipeline.
readOnly: false
# Which network ingress you are using in your Kubernetes Cluster
# Leaving the ingress empty will disable the ingress.
@@ -42,10 +42,10 @@ mongodb:
username: "root"
password: "yourpassword"
# A MQTT broker (vernemq or other like mosquitto) is used to have a bi-directional
# communication between Kerberos Agents and Kerberos Hub.
# communication between Agents and Hub.
# we recommend to use vernemq (as part of this installation), but a stand-alone mosquitto broker is also possible.
mqtt:
host: "mqtt.yourdomain.com" # this needs to be a public accessible DNS name (it's used to communicate between Kerberos Agents and Kerberos Hub)
host: "mqtt.yourdomain.com" # this needs to be a public accessible DNS name (it's used to communicate between Agents and Hub)
port: "31443"
protocol: "wss"
username: "yourusername"
@@ -85,29 +85,29 @@ turn:
openai:
enabled: false
apikey: "xxx"
# We have a kerberos vault component installed which contains all the
# recordings. Kerberos vault is queried to retrieve the recordings
# We have a Vault component installed which contains all the
# recordings. Vault is queried to retrieve the recordings
# from the appropriate provider.
kerberosvault:
#uri: "http://vault-lb.kerberos-vault" # this needs to be a public accessible DNS name.
uri: http://vault-nodeport.kerberos-vault:80/api
provider: "minio"
provider: "garage"
accesskey: "XJoi2@bgSOvOYBy#"
secretkey: "OGGqat4lXRpL@9XBYc8FUaId@5"
# Archiving is used when creating a task. The underlying recording of the task will be copied from its
# existing provider to the below archived provider. Seperate credentials are used, as it makes possible to
# specify another retention period.
archive:
provider: "minio"
provider: "garage"
accesskey: "XJoi2@bgSOvOYBy#"
secretkey: "OGGqat4lXRpL@9XBYc8FUaId@5"
thumbnail:
provider: "minio"
provider: "garage"
accessKey: "XJoi2@bgSOvOYBy#"
secretKey: "OGGqat4lXRpL@9XBYc8FUaId@5"
sprite:
provider: "minio"
provider: "garage"
accessKey: "XJoi2@bgSOvOYBy#"
secretKey: "OGGqat4lXRpL@9XBYc8FUaId@5"
email:
@@ -124,21 +124,23 @@ email:
password: "yourpassword"
templates:
welcome: "welcome"
welcomeTitle: "Welcome to Kerberos Hub"
welcomeTitle: "Welcome to Hub"
activate: "activate"
activateTitle: "Wonderful! Your Kerberos Hub is now active"
activateTitle: "Wonderful! Your Hub is now active"
forgot: "forgot"
forgotTitle: "Password reset Kerberos Hub. You forgot your password"
forgotTitle: "Password reset Hub. You forgot your password"
share: "share"
shareTitle: "[Action] You received a recording from Kerberos Hub"
shareTitle: "[Action] You received a recording from Hub"
caseShare: "share_case"
caseShareTitle: "[Action] A case has been shared with you on Hub"
detection: "detection"
disabled: "disabled"
highupload: "highupload"
device: "device"
alertTitle: "[Alert] Kerberos Hub detected something an event"
deviceTitle: "[Device] A Kerberos Agent's status has been changed"
alertTitle: "[Alert] Hub detected something an event"
deviceTitle: "[Device] An Agent's status has been changed"
# Following are all the different deployments needed to make
# Kerberos hub properly working.
# Hub properly working.
kerberoshub:
admin:
repository: uugai/admin
@@ -153,9 +155,11 @@ kerberoshub:
memory: 100Mi
cpu: 100m
api:
# Chart 0.170.1 supplies non-root security contexts and writable /tmp.
# Retain hub-api-tmp when overriding the chart's volume/mount lists.
repository: ghcr.io/uug-ai/hub-api
pullPolicy: IfNotPresent
tag: "v1.8.6"
tag: "v1.11.31"
replicas: 1
jwtSecret: "I1JcwzW3A0t_THIS-IS_NOT_A_SECRET_WJK9jnPkipbnVTpf0efMy" # change to a random value, this is for generating JWT tokens.
schema: "http"
@@ -180,8 +184,8 @@ kerberoshub:
#legacyUrl: "api.legacy.yourdomain.com"
# MFA issuer name
mfaIssuer: "Kerberos.io"
# Admin API's are made available for automation of Kerberos Hub.
mfaIssuer: "Hub"
# Admin API's are made available for automation of Hub.
# To access those API's (e.g. creation of owner users), an API key needs to be provided.
apiKey: "Z6GPfDdYj8mxLyy6iUJVf9yBlri9lhsW"
## Certificates
@@ -197,20 +201,20 @@ kerberoshub:
# secretName: kerberoshub-admin
language: "english"
fallbackLanguage: "english"
# Legacy (reseller) it is possible to link to AWS S3 and IAM (however Kerberos Vault is now the recommended option).
# This is primarily used for creation of subscriptions, and not needed if you are using mainly Kerberos Vault.
# Legacy (reseller) it is possible to link to AWS S3 and IAM (however Vault is now the recommended option).
# This is primarily used for creation of subscriptions, and not needed if you are using mainly Vault.
aws:
region: "xxx"
bucket: "xxx"
accessKey: "xxx"
secretKey: "xxx"
stripe: # We use stripe for billing, so it's possible to resell Kerberos Hub if agreed.
stripe: # We use stripe for billing, so it's possible to resell Hub if agreed.
privateKey: "xxx"
slack: # Slack is used in the api, to send logs to a specific Slack channel.
enabled: "true"
hook: "yourslackhook" # https://hooks.slack.com/services/T08Q2Q9V5/xxKT/JALxxAk26bHtuqTfZ
username: "Kerberos Hub" # The slack username
elasticsearch: # Logs of the kerberos hub will be send to an elastic search cluster.
username: "Hub" # The slack username
elasticsearch: # Logs of the Hub will be send to an elastic search cluster.
enabled: "false"
protocol: "http"
host: "yourelasticsearchinstance.com"
@@ -238,10 +242,15 @@ kerberoshub:
frontend:
repository: ghcr.io/uug-ai/hub-frontend
pullPolicy: IfNotPresent
tag: "v1.7.15"
tag: "v1.19.28"
# Requires Hub chart 0.170.0+ for the matching hardened runtime defaults.
containerPort: 8080
replicas: 1
schema: "http"
url: "yourdomain.com"
# The front-end but in read-only mode
demoEnabled: true
demoUrl: "app-demo.kerberos.io"
resources:
requests:
memory: 100Mi
@@ -268,7 +277,7 @@ kerberoshub:
apikey: "xxx"
sentry: # We can trace errors in Sentry
url: "https://xxx@sentry.io/xxx"
stripe: # We use stripe for billing, so it's possible to resell Kerberos Hub if agreed.
stripe: # We use stripe for billing, so it's possible to resell Hub if agreed.
publicKey: ""
googlemaps: # Google maps is used inside the application to visualise cameras and sites.
apikey: "xxxx"
@@ -277,19 +286,19 @@ kerberoshub:
posthog: # Posthog is used for auditing and user interaction logging
key: "xxx"
url: "https://posthog.domain.com"
# You can disable the Kerberos agent buttons, this make sense
# in a white-label setup, or where you are managing the Kerberos Agents for your customers.
# You can disable the Agent buttons, this make sense
# in a white-label setup, or where you are managing the Agents for your customers.
hideAddAgent: "true"
# Multi tenancy (domains)
# By default the Kerberos Hub allows multi-tenancy through the concept
# By default the Hub allows multi-tenancy through the concept
# of accounts and subaccounts. However through the concept of domains, you
# take it a step further. Within a domain, user accounts are unique, and are prefixed by a (domain\).
#multiTenant: true
#tenantBaseDomain: "yourdomain.com" # this would resolve in following sub domain "https://domain.kerberos.io"
# Page title (browser)
title: "Kerberos Hub - Video surveillance as it should be"
# You can style Kerberos hub as you wish.
title: "Hub - Video surveillance as it should be"
# You can style Hub as you wish.
# 1. we do the styling on our side and bake it in the Docker image (change the logo attribute to your company name)
# 2. you bring your own logo (set logo to 'custom'), and mount the css file and favicons.
# we will need to include your logo in the Docker image, so please reach out to us.
@@ -305,7 +314,7 @@ kerberoshub:
# persistentVolumeClaim:
# claimName: custom-layout-claim
# By specifying the below environments variables, you can tweak the
# headings and paragraphs of Kerberos Hub front-end.
# headings and paragraphs of Hub front-end.
# Login page
loginDescription: ""
loginCopyright: ""
@@ -348,17 +357,17 @@ kerberoshub:
organization: "github-organization"
team: "github-team"
cleanup:
repository: uugai/hub-cleanup
repository: ghcr.io/uug-ai/hub-cleanup
pullPolicy: IfNotPresent
tag: "v1.4.1"
tag: "v1.4.26"
resources:
requests:
memory: 50Mi
cpu: 50m
monitordevice:
repository: uugai/hub-monitor-device
repository: ghcr.io/uug-ai/hub-monitor-device
pullPolicy: IfNotPresent
tag: "v1.3.0"
tag: "v1.4.10"
resources:
requests:
memory: 50Mi
@@ -408,9 +417,9 @@ kerberoshub:
cpu: 50m
kerberospipeline:
event:
repository: uugai/hub-pipeline-event
repository: ghcr.io/uug-ai/hub-pipeline-event
pullPolicy: IfNotPresent
tag: "v1.2.0"
tag: "v1.3.9"
replicas: 1
resources:
requests:
@@ -422,7 +431,7 @@ kerberospipeline:
monitor:
repository: ghcr.io/uug-ai/hub-pipeline-monitor
pullPolicy: IfNotPresent
tag: "v1.3.9"
tag: "v1.3.29"
replicas: 1
resources:
requests:
@@ -434,7 +443,7 @@ kerberospipeline:
sequence:
repository: ghcr.io/uug-ai/hub-pipeline-sequence
pullPolicy: IfNotPresent
tag: "v1.6.9"
tag: "v1.7.14"
replicas: 1
resources:
requests:
@@ -444,9 +453,9 @@ kerberospipeline:
memory: 50Mi
cpu: 50m
throttler:
repository: uugai/hub-pipeline-throttler
repository: ghcr.io/uug-ai/hub-pipeline-throttler
pullPolicy: IfNotPresent
tag: "v1.2.0"
tag: "v1.2.10"
replicas: 1
resources:
requests:
@@ -458,7 +467,7 @@ kerberospipeline:
notify:
repository: ghcr.io/uug-ai/hub-pipeline-notification
pullPolicy: IfNotPresent
tag: "v1.1.5"
tag: "v1.3.46"
replicas: 1
resources:
requests:
@@ -476,9 +485,9 @@ kerberospipeline:
# persistentVolumeClaim:
# claimName: custom-layout-claim
notifyTest:
repository: uugai/hub-pipeline-notification-test
repository: ghcr.io/uug-ai/hub-pipeline-notification-test
pullPolicy: IfNotPresent
tag: "v1.2.1"
tag: "v1.2.6"
replicas: 1
resources:
requests:
@@ -498,7 +507,7 @@ kerberospipeline:
analysis:
repository: ghcr.io/uug-ai/hub-pipeline-analysis
pullPolicy: IfNotPresent
tag: "v1.7.6"
tag: "v1.8.29"
replicas: 1
resources:
requests:
@@ -510,7 +519,7 @@ kerberospipeline:
dominantColor:
repository: ghcr.io/uug-ai/hub-pipeline-dominantcolors
pullPolicy: IfNotPresent
tag: "v2.0.2"
tag: "v2.0.9"
replicas: 1
resources:
requests:
@@ -522,13 +531,13 @@ kerberospipeline:
thumbnail:
repository: ghcr.io/uug-ai/hub-pipeline-thumbnail
pullPolicy: IfNotPresent
tag: "v1.3.1"
tag: "v1.3.22"
replicas: 1
quality: "1" # 1 (best) - 31 (worst)
width: "600"
height: "-1"
kerberosvault:
enabled: true # If you want to use Kerberos Vault to store the thumbnails
enabled: true # If you want to use Vault to store the thumbnails
resources:
requests:
memory: 50Mi
@@ -537,9 +546,9 @@ kerberospipeline:
memory: 50Mi
cpu: 50m
counting:
repository: uugai/hub-pipeline-counting
repository: ghcr.io/uug-ai/hub-pipeline-counting
pullPolicy: IfNotPresent
tag: "v1.6.3"
tag: "v2.0.6"
replicas: 1
resources:
requests:
@@ -547,9 +556,9 @@ kerberospipeline:
cpu: 50m
sprite:
enabled: true # Enable or disable the sprite generation 'true' or 'false
repository: uugai/hub-pipeline-sprite
repository: ghcr.io/uug-ai/hub-pipeline-sprite
pullPolicy: IfNotPresent
tag: "v1.0.9"
tag: "v1.1.27"
replicas: 1
interval: "1" # Number of secondes between each thumbnail in the sprite
width: "240" # Should not be changed for the moment (hard coded in UI)
@@ -562,9 +571,9 @@ kerberospipeline:
memory: 50Mi
cpu: 50m
export:
repository: kerberos/pipe-export
repository: ghcr.io/uug-ai/hub-pipeline-export
pullPolicy: IfNotPresent
tag: "1.0.6437127898"
tag: "v1.2.20"
replicas: 1
resources:
requests:
+6 -6
View File
@@ -1,22 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: hub-yolov8
name: hub-pipeline-classifier
labels:
app: hub-yolov8
app: hub-pipeline-classifier
spec:
replicas: 1
selector:
matchLabels:
app: hub-yolov8
app: hub-pipeline-classifier
template:
metadata:
labels:
app: hub-yolov8
app: hub-pipeline-classifier
spec:
containers:
- name: hub-yolov8
image: uugai/hub-yolov8:latest
- name: hub-pipeline-classifier
image: ghcr.io/uug-ai/hub-pipeline-classifier:v1.5.5
#resources:
# limits:
# nvidia.com/gpu: 1 # requesting a single GPU
+1 -1
View File
@@ -6,4 +6,4 @@ resources:
- factory
- vault
- hub
- minio
- garage
+340
View File
@@ -0,0 +1,340 @@
<!-- markdownlint-disable MD013 -->
# Vault base manifests
This directory holds the raw Kubernetes manifests for **Vault**. Vault receives
recordings from Agents, stores recording metadata in MongoDB, and sends
recording bytes to storage providers configured through the Vault UI.
> **Scope of this README.** At the top level of this repository Vault is normally
> installed through **Kustomize** (see [`overlays/`](../../overlays) and
> [`README.kustomize.md`](../../README.kustomize.md)). This README documents the
> alternative: applying the manifests in this folder directly with `kubectl`.
> Use it when you want to install only Vault, inspect each object, or integrate
> these manifests into your own deployment tooling.
## What gets deployed
| File | Kind | Purpose |
| ---- | ---- | ------- |
| [`mongodb-configmap.yaml`](./mongodb-configmap.yaml) | `ConfigMap` | Supplies Vault's MongoDB connection, backend flavor, retry-write, and TLS settings. |
| [`kerberos-vault-deployment.yaml`](./kerberos-vault-deployment.yaml) | `Deployment` | ConfigMap-backed Vault example selected by the base Kustomization. |
| [`kerberos-vault-deployment-with-secrets.yaml`](./kerberos-vault-deployment-with-secrets.yaml) | `Deployment` | Alternative Secret-backed Vault example. |
| [`kerberos-vault-service.yaml`](./kerberos-vault-service.yaml) | `Service` | Exposes Vault on `NodePort` **30080**. A commented `LoadBalancer` variant is included. |
| [`data-filtering-deployment.yaml`](./data-filtering-deployment.yaml) | `Deployment` | Optional YOLO data-filtering worker. The default manifest requests one NVIDIA GPU. |
| [`vault-runtime-configuration-configmap.yaml`](./vault-runtime-configuration-configmap.yaml) | `ConfigMap` | Plaintext local defaults used by the base Kustomization; managed-resource lists are empty. |
| [`vault-runtime-configuration-secret.yaml`](./vault-runtime-configuration-secret.yaml) | `Secret` | Optional provider, integration, and account example for production secret handling. |
The matching `kerberos-vault` namespace is defined in
[`../namespaces/kerberos-vault.yaml`](../namespaces/kerberos-vault.yaml).
## Hardened Vault image
Both deployment variants use the hardened Vault image
`ghcr.io/uug-ai/vault:v1.7.19`. The Vault container runs as non-root UID/GID
`65532`, drops Linux capabilities, disables privilege escalation, uses the
`RuntimeDefault` seccomp profile, and has a read-only root filesystem.
The image listens on container port `8080`. The Service keeps its existing
port `80` and NodePort `30080`, forwarding to the named `http` container port.
Startup, readiness, and liveness probes use `/api/health`.
The image uses `/run/vault` for temporary upload/finalization files. Each
Deployment mounts a writable, disk-backed `emptyDir` there, limited to `1Gi`;
the pod `fsGroup` grants the non-root Vault process access. Size this scratch
limit for expected concurrent uploads and available node ephemeral storage.
### Size the writable scratch volume
There is no one-size-fits-all volume size: estimate how many full recordings
Vault may be writing to scratch at the same time, and the maximum size of each.
- A regular HTTP upload is spooled to a temporary file. The default
`VAULT_MAX_PUBLISH_FILE_BYTES` limit is **1 GiB per upload**. If you change
this limit, use the new value in your sizing estimate.
- Resumable tus chunks are stored in the configured object-storage provider,
not in this volume. When an upload is finalized, Vault downloads the completed
recording to a temporary file, so count concurrent **tus finalizations** and
their maximum recording sizes.
- Set `TUS_MAX_SIZE_BYTES` to a positive byte limit to cap tus upload size. It is
unlimited when unset. For example, `1073741824` limits each tus upload to 1
GiB. Keep this cap within the scratch capacity you have planned.
Use this conservative estimate, then round up to a convenient GiB value:
```text
scratch size >= 1.5 x (
concurrent HTTP uploads x maximum HTTP upload size
+ concurrent tus finalizations x maximum tus upload size
)
```
The `1.5` multiplier allows roughly 50% headroom. For example, allowing two
simultaneous 1 GiB HTTP uploads and two simultaneous 1 GiB tus finalizations
uses up to 4 GiB for those temporary files; choose a scratch limit of at least
**6 GiB**. If you only expect two full-file operations at once in total, a
**3 GiB** limit is a reasonable starting point. Increase it for larger files or
more concurrent operations.
To apply the 6 GiB example, set the tus cap and scratch limit in the Vault
container and pod template of the Deployment you use. Make the same changes to
the Secret-backed Deployment if you use that variant:
```yaml
spec:
template:
spec:
containers:
- name: vault
env:
- name: TUS_MAX_SIZE_BYTES
value: "1073741824"
volumeMounts:
- name: vault-tmp
mountPath: /run/vault
volumes:
- name: vault-tmp
emptyDir:
sizeLimit: 6Gi
```
Keep the existing `GIN_MODE` and other environment variables and the
`vault-tmp` mount; merge these entries rather than replacing them. The disk-backed
`emptyDir` is charged to node ephemeral storage: its `sizeLimit` is a ceiling,
not a reservation. Make sure the node and your pod's ephemeral-storage
requests/limits can accommodate scratch use as well as container logs and other
temporary files. If the limit is exhausted, uploads or finalizations can fail.
Apply the updated Deployment and check that the Vault pod becomes Ready before
relying on the new limits.
## Prerequisites
- A running Kubernetes cluster and `kubectl` configured to reach it.
- A MongoDB-compatible database reachable from the Vault pod.
- A storage provider such as MinIO, Amazon S3, Google Cloud Storage, or Azure
Blob Storage. Configure it in the Vault UI or through the optional runtime
configuration ConfigMap or Secret after deployment.
- An NVIDIA-capable node and device plugin only when deploying the optional
data-filtering worker with its default resource settings.
## MongoDB configuration
Vault imports every key from the `mongodb` ConfigMap through `envFrom`. The base
manifest connects to the in-cluster MongoDB service at `mongodb.mongodb`.
| Variable | Default here | Meaning |
| -------- | ------------ | ------- |
| `MONGODB_DATABASE_STORAGE` | `KerberosStorage` | Database where Vault stores recording metadata and configuration. |
| `MONGODB_URI` | unset | Complete MongoDB connection URI. When set, it takes precedence over component settings. |
| `MONGODB_HOST` | `mongodb.mongodb` | MongoDB host and optional port for component-based configuration. |
| `MONGODB_DATABASE_CREDENTIALS` | `admin` | Authentication database or auth source. |
| `MONGODB_USERNAME` / `MONGODB_PASSWORD` | `root` / `yourpassword` | Database credentials. Replace these demo values before deployment. |
| `MONGODB_FLAVOR` | `mongodb` | Backend compatibility mode: `mongodb` or `documentdb`. |
| `MONGODB_RETRY_WRITES` | `true` | Enables retryable writes for MongoDB. Vault always disables them for DocumentDB. |
| `MONGODB_TLS` | `false` | Enables TLS for the database connection. |
| `MONGODB_TLS_CA_FILE` | empty | Path to a mounted PEM CA bundle. A non-empty value also enables TLS. |
| `MONGODB_TLS_INSECURE_SKIP_VERIFY` | `false` | Disables certificate and hostname verification. Use only for isolated local testing. |
The ConfigMap also contains legacy Factory and Hub database names, but Vault uses
`MONGODB_DATABASE_STORAGE` for its own data.
> [!WARNING]
> ConfigMaps are not appropriate for production credentials. Move the MongoDB
> username, password, or credential-bearing URI to a Kubernetes `Secret` in a
> production deployment and expose those keys to the Vault container.
### MongoDB Atlas or another URI connection
Set `MONGODB_URI` in [`mongodb-configmap.yaml`](./mongodb-configmap.yaml). The
component settings are ignored when the URI is non-empty.
The Deployment's `wait-for-mongodb-before-starup` init container currently probes
the base service name `mongodb.mongodb:27017`. When using Atlas or another external
database, change that command to probe the external host and port, or replace it
with a readiness mechanism suitable for your environment. Otherwise Vault will
remain in `Init` even when its configured database is reachable.
### DocumentDB or MongoDB with a custom CA
For AWS DocumentDB, use a connection URI, set the flavor to `documentdb`, disable
retryable writes, and enable TLS:
```yaml
MONGODB_URI: "mongodb://<username>:<password>@<endpoint>:27017/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false"
MONGODB_FLAVOR: "documentdb"
MONGODB_RETRY_WRITES: "false"
MONGODB_TLS: "true"
MONGODB_TLS_CA_FILE: "/certs/global-bundle.pem"
MONGODB_TLS_INSECURE_SKIP_VERIFY: "false"
```
Create a Secret from the trusted CA bundle:
```bash
kubectl create namespace kerberos-vault --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic mongodb-ca \
--from-file=global-bundle.pem \
-n kerberos-vault
```
Then add the Secret volume and mount to
[`kerberos-vault-deployment.yaml`](./kerberos-vault-deployment.yaml):
```yaml
spec:
template:
spec:
containers:
- name: vault
volumeMounts:
- name: mongodb-ca
mountPath: /certs
readOnly: true
volumes:
- name: mongodb-ca
secret:
secretName: mongodb-ca
```
Also update or remove the MongoDB wait init-container as described above. For
Amazon DocumentDB, the shared Vault database client defaults component-based
authentication to `SCRAM-SHA-1` and does not enable MongoDB Stable API.
## Vault configuration
The selected runtime ConfigMap or Secret supplies these environment variables
through `envFrom`. Only `GIN_MODE` remains directly on either Deployment:
| Variable | Default here | Meaning |
| -------- | ------------ | ------- |
| `KERBEROS_LOGIN_USERNAME` / `KERBEROS_LOGIN_PASSWORD` | `root` / `kerberos` | Vault UI login. Change these demo credentials. |
| `MQTT_URI` | `tcp://mqtt.kerberos.io:1883` | MQTT broker used for on-demand forwarding. |
| `MQTT_USERNAME` / `MQTT_PASSWORD` | empty | Optional MQTT credentials. |
| `VAULT_STORAGE_PROVIDERS` | unset | JSON array of runtime-managed storage providers. |
| `VAULT_INTEGRATIONS` | unset | JSON array of runtime-managed integrations. |
| `VAULT_ACCOUNTS` | unset | JSON array of runtime-managed Vault accounts. |
Vault accounts, storage providers, and integrations can be supplied as JSON
arrays through the three environment variables. Two alternative Deployment
manifests show how to import them from a `vault-runtime-configuration` ConfigMap
or Secret.
Environment definitions are grayed out and read-only in Vault, override database
definitions with the same name, and are loaded when the pod starts. Invalid
definitions prevent startup. An enabled Vault integration automatically starts
the forwarding worker. See [`README.configure.md`](../../README.configure.md).
The base [`kustomization.yaml`](./kustomization.yaml) includes the plaintext
ConfigMap and standard Deployment so the demo login remains usable. The
credential-bearing Secret and Secret-backed Deployment remain standalone:
- [`vault-runtime-configuration-configmap.yaml`](./vault-runtime-configuration-configmap.yaml)
contains the demo Vault login and MQTT defaults. Its provider, integration,
and account arrays are `[]`, so those resources remain managed through the UI
and database. ConfigMap values are plaintext.
- [`vault-runtime-configuration-secret.yaml`](./vault-runtime-configuration-secret.yaml)
stores the same eight variables with `stringData`, so values can be entered
without manual base64 encoding. Use an external secret manager or encrypted
GitOps workflow in production.
For direct `kubectl` installation, apply one matching pair in the Vault
namespace. Replace every Secret example credential first. Both Deployment
manifests manage `Deployment/vault`; choose one and do not apply both.
```bash
# Plaintext local-development variant:
kubectl apply -n kerberos-vault \
-f ./vault-runtime-configuration-configmap.yaml
kubectl apply -n kerberos-vault \
-f ./kerberos-vault-deployment.yaml
# Recommended Secret-backed variant:
kubectl apply -n kerberos-vault \
-f ./vault-runtime-configuration-secret.yaml
kubectl apply -n kerberos-vault \
-f ./kerberos-vault-deployment-with-secrets.yaml
```
Restart the Deployment after changing any value. Leave a key absent or set it
to `[]` to use only database definitions for that resource type. Switching
variants is also an `apply`: the selected manifest replaces the existing
`Deployment/vault` definition.
## Deploy with kubectl without Kustomize
The manifest files do not hard-code a namespace because Kustomize normally
injects it. When applying them directly, target the namespace explicitly.
```bash
# 1. Create the namespace.
kubectl apply -f ../namespaces/kerberos-vault.yaml
# 2. Review the database settings, then create the ConfigMap.
kubectl apply -n kerberos-vault -f ./mongodb-configmap.yaml
# 3. Optionally apply the plaintext runtime configuration, then deploy Vault.
# For the Secret-backed alternative, use the matching pair documented above.
# kubectl apply -n kerberos-vault -f ./vault-runtime-configuration-configmap.yaml
kubectl apply -n kerberos-vault -f ./kerberos-vault-deployment.yaml
kubectl apply -n kerberos-vault -f ./kerberos-vault-service.yaml
```
Verify the rollout:
```bash
kubectl get pods,svc -n kerberos-vault
kubectl rollout status deployment/vault -n kerberos-vault
kubectl logs -n kerberos-vault deploy/vault
```
If the pod remains in `Init`, inspect the database wait container:
```bash
kubectl logs -n kerberos-vault deploy/vault \
-c wait-for-mongodb-before-starup
```
## Access the UI
With the `NodePort` service, Vault is reachable on port **30080** of any node:
```bash
kubectl get nodes -o wide
# Browse http://<node-ip>:30080
# Or use a local port-forward without exposing a node port.
kubectl port-forward -n kerberos-vault svc/vault-nodeport 8080:80
# Browse http://localhost:8080
```
Log in with the configured `KERBEROS_LOGIN_USERNAME` and
`KERBEROS_LOGIN_PASSWORD` values. The base defaults are `root` and `kerberos`.
To use a cloud `LoadBalancer`, uncomment the `vault-lb` service at the bottom of
[`kerberos-vault-service.yaml`](./kerberos-vault-service.yaml) and remove or
comment out the `NodePort` service.
## Optional data filtering
The data-filtering worker is independent of the Vault Deployment. Before applying
it, configure its queue, Vault credentials, model settings, and GPU resources in
[`data-filtering-deployment.yaml`](./data-filtering-deployment.yaml).
```bash
kubectl apply -n kerberos-vault -f ./data-filtering-deployment.yaml
kubectl rollout status deployment/data-filtering -n kerberos-vault
```
Clusters without an NVIDIA device plugin must remove the `nvidia.com/gpu`
requests and limits or leave this optional Deployment unapplied. See
[`README.extensions.md`](../../README.extensions.md) for the integration flow.
## Uninstall
```bash
kubectl delete -n kerberos-vault -f ./data-filtering-deployment.yaml --ignore-not-found
kubectl delete -n kerberos-vault -f ./kerberos-vault-service.yaml
kubectl delete -n kerberos-vault -f ./kerberos-vault-deployment.yaml
kubectl delete -n kerberos-vault -f ./mongodb-configmap.yaml
kubectl delete secret mongodb-ca -n kerberos-vault --ignore-not-found
kubectl delete -f ../namespaces/kerberos-vault.yaml
```
@@ -0,0 +1,96 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault
spec:
replicas: 1
selector:
matchLabels:
app: vault
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: vault
spec:
securityContext:
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
initContainers:
- name: wait-for-mongodb-before-starup
image: busybox
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
command: ["sh", "-c", "until nc -z mongodb.mongodb 27017 > /dev/null; do echo Waiting for master.; sleep 2; done;"]
containers:
- name: vault
image: ghcr.io/uug-ai/vault:v1.7.19
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 128Mi
cpu: 100m
ports:
- name: http
containerPort: 8080
protocol: TCP
startupProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 30
readinessProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 30
timeoutSeconds: 3
failureThreshold: 3
volumeMounts:
- name: vault-tmp
mountPath: /run/vault
envFrom:
- configMapRef:
name: mongodb
# Optional runtime configuration containing provider, integration,
# and account credentials.
- secretRef:
name: vault-runtime-configuration
optional: true
env:
- name: GIN_MODE
value: release
volumes:
- name: vault-tmp
emptyDir:
sizeLimit: 1Gi
+55 -32
View File
@@ -18,13 +18,32 @@ spec:
labels:
app: vault
spec:
securityContext:
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
initContainers:
- name: wait-for-mongodb-before-starup
image: busybox
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
command: ["sh", "-c", "until nc -z mongodb.mongodb 27017 > /dev/null; do echo Waiting for master.; sleep 2; done;"]
containers:
- name: vault
image: ghcr.io/uug-ai/vault:v1.2.1
image: ghcr.io/uug-ai/vault:v1.7.19
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
resources:
requests:
memory: 128Mi
@@ -33,40 +52,44 @@ spec:
memory: 128Mi
cpu: 100m
ports:
- containerPort: 80
- name: http
containerPort: 8080
protocol: TCP
startupProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 30
readinessProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: http
periodSeconds: 30
timeoutSeconds: 3
failureThreshold: 3
volumeMounts:
- name: vault-tmp
mountPath: /run/vault
envFrom:
- configMapRef:
name: mongodb
# Optional plaintext runtime configuration for local development.
- configMapRef:
name: vault-runtime-configuration
optional: true
env:
- name: GIN_MODE
value: release
- name: KERBEROS_LOGIN_USERNAME
value: "root"
- name: KERBEROS_LOGIN_PASSWORD
value: "kerberos"
# MQTT broker to be used for on-demand forwarding.
- name: MQTTURI
value: "tcp://mqtt.kerberos.io:1883"
- name: MQTT_USERNAME
value: ""
- name: MQTT_PASSWORD
value: ""
# If you have a chained vault setup, enable this.
- name: CONTINUOUS_FORWARDING
value: "false"
# MongoDB URI (for example for a SaaS service like MongoDB Atlas)
# If uri is set, the below properties are not used (host, adminDatabase, username, password)
#MONGODB_URI: "mongodb+srv://xx:xx@kerberos-hub.xxx.mongodb.net/?retryWrites=true&w=majority&appName=xxx"
# Mongodb configuration (if you are not using the configmap)
# This is the mongodb database where data will be stored, you might use a different name if you want.
#- name: MONGODB_DATABASE_STORAGE
# value : "KerberosStorage"
# If you do not wish to use the URI, you can specify the individual values.
#- name: MONGODB_HOST
# value: "mongodb.mongodb"
#- name: MONGODB_DATABASE_CREDENTIALS
# value: "admin"
#- name: MONGODB_USERNAME
# value: "root"
#- name: MONGODB_PASSWORD
# value: "yourmongodbpassword"
volumes:
- name: vault-tmp
emptyDir:
sizeLimit: 1Gi
+2 -2
View File
@@ -10,7 +10,7 @@ spec:
type: NodePort
ports:
- port: 80
targetPort: 80
targetPort: http
nodePort: 30080 # You can specify a port in the range 30000-32767 or let Kubernetes assign one automatically
name: frontend
protocol: TCP
@@ -27,7 +27,7 @@ spec:
# type: LoadBalancer
# ports:
# - port: 80
# targetPort: 80
# targetPort: http
# name: frontend
# protocol: TCP
# selector:
+1
View File
@@ -1,6 +1,7 @@
namespace: kerberos-vault
resources:
- mongodb-configmap.yaml
- vault-runtime-configuration-configmap.yaml
- kerberos-vault-deployment.yaml
- kerberos-vault-service.yaml
- data-filtering-deployment.yaml
+13 -1
View File
@@ -15,4 +15,16 @@ data:
MONGODB_HOST: "mongodb.mongodb"
MONGODB_DATABASE_CREDENTIALS: "admin"
MONGODB_USERNAME: "root"
MONGODB_PASSWORD: "yourpassword"
MONGODB_PASSWORD: "yourpassword"
# MongoDB-compatible backend and connection behavior.
# Use "documentdb" for AWS DocumentDB; retryable writes are always disabled
# by Vault for that flavor.
MONGODB_FLAVOR: "mongodb"
MONGODB_RETRY_WRITES: "true"
# TLS is disabled by default. A non-empty CA file path also enables TLS, but
# the referenced PEM bundle must be mounted into the Vault container.
MONGODB_TLS: "false"
MONGODB_TLS_CA_FILE: ""
MONGODB_TLS_INSECURE_SKIP_VERIFY: "false"
@@ -0,0 +1,16 @@
# Plaintext defaults for local development. Provider, integration, and account
# lists stay empty so credentials can be managed through the Vault UI/database.
# Use vault-runtime-configuration-secret.yaml for environment-managed resources.
apiVersion: v1
kind: ConfigMap
metadata:
name: vault-runtime-configuration
data:
KERBEROS_LOGIN_USERNAME: "root"
KERBEROS_LOGIN_PASSWORD: "kerberos"
MQTT_URI: "tcp://mqtt.kerberos.io:1883"
MQTT_USERNAME: ""
MQTT_PASSWORD: ""
VAULT_STORAGE_PROVIDERS: "[]"
VAULT_INTEGRATIONS: "[]"
VAULT_ACCOUNTS: "[]"
@@ -0,0 +1,19 @@
# Replace all example credentials before applying this manifest.
# Keep the populated file outside source control in production.
apiVersion: v1
kind: Secret
metadata:
name: vault-runtime-configuration
type: Opaque
stringData:
KERBEROS_LOGIN_USERNAME: "root"
KERBEROS_LOGIN_PASSWORD: "kerberos"
MQTT_URI: "tcp://mqtt.kerberos.io:1883"
MQTT_USERNAME: ""
MQTT_PASSWORD: ""
VAULT_STORAGE_PROVIDERS: >-
[{"name":"garage","provider":"minio","host":"garage.garage:3900","region":"garage","bucket":"mybucket","access_key":"replace-with-garage-access-key","secret":"replace-with-garage-secret-key","use_ssl":"false"}]
VAULT_INTEGRATIONS: >-
[{"name":"events","queue":"rabbitmq","broker":"amqp://rabbitmq.rabbitmq.svc:5672","topic":"recordings","username":"example-user","password":"example-password"}]
VAULT_ACCOUNTS: >-
[{"account":"camera-fleet","directory":"fleet","provider":"garage","access_key":"example-account-key","secret_access_key":"example-account-secret","limit":"30","queues":["events"]}]
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Kerberos.io",
"short_name": "Kerberos.io",
"name": "Hub",
"short_name": "Hub",
"icons": [
{
"src": "/favicons/android-chrome-192x192.png?v=1",
+5 -5
View File
@@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -150,7 +150,7 @@ margin-bottom: 36px!important;
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -160,7 +160,7 @@ line-height: 24px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -260,7 +260,7 @@ mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #6D6666;
margin-top: 12px;">You're ready. Go ahead and <a href='{{link}}'>login to your Kerberos Hub account</a>. Start exploring the different features and functions to support your business usecases.</p>
margin-top: 12px;">You're ready. Go ahead and <a href='{{link}}'>login to your Hub account</a>. Start exploring the different features and functions to support your business usecases.</p>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -356,7 +356,7 @@ line-height: 36px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+10 -10
View File
@@ -1,16 +1,16 @@
package templates
var TEMPLATE_DETECTION_TEXT = `
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -20,7 +20,7 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
`
@@ -35,7 +35,7 @@ var TEMPLATE_DETECTION = `
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -178,7 +178,7 @@ var TEMPLATE_DETECTION = `
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -188,7 +188,7 @@ var TEMPLATE_DETECTION = `
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -304,7 +304,7 @@ var TEMPLATE_DETECTION = `
text-align: left;
color: #6D6666;
margin-top: 12px;">An alert was send to your e-mail as one of the conditions was triggered for your device <b>{{devicename}}</b>. Please watch the recording, by clicking on below button.
If you believe this event is a false positive, go to your Kerberos Hub account and change the alert settings accordingly.</p>
If you believe this event is a false positive, go to your Hub account and change the alert settings accordingly.</p>
<p style=" font-family: Inter;
@@ -426,7 +426,7 @@ If you believe this event is a false positive, go to your Kerberos Hub account a
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+9 -9
View File
@@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -150,7 +150,7 @@
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -160,7 +160,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -225,7 +225,7 @@
margin-bottom: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;">One of your Kerberos Agents changed</h2>
padding-right: 0;">One of your Agents changed</h2>
<h2 style=" font-family: Inter;
font-size: 20px;
font-style: normal;
@@ -265,7 +265,7 @@
letter-spacing: 0em;
text-align: left;
color: #262424;
width: 480px">The status your Kerberos Agent changed</h3>
width: 480px">The status your Agent changed</h3>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -275,8 +275,8 @@
letter-spacing: 0em;
text-align: left;
color: #6D6666;
margin-top: 12px;">Kerberos Agents go offline due to a variety of reasons. The machine, node, micro controller on which your Kerberos Agent runs, gets corrupted or disconnected from the internet.
The camera itself is broken, damaged or in the worst case tampered. Have a look into your Kerberos Hub account for the latest recordings and/or verify the connection and status of your Kerberos Agent.</p>
margin-top: 12px;">Agents go offline due to a variety of reasons. The machine, node, micro controller on which your Agent runs, gets corrupted or disconnected from the internet.
The camera itself is broken, damaged or in the worst case tampered. Have a look into your Hub account for the latest recordings and/or verify the connection and status of your Agent.</p>
<a style="text-decoration: none;color: none;" href="{{link}}">
<p style="font-family: Inter;
@@ -295,7 +295,7 @@ The camera itself is broken, damaged or in the worst case tampered. Have a look
width: 160px;
border-radius: 4px;
text-align: center;
cursor: pointer;">Go to Kerberos Hub -></p>
cursor: pointer;">Go to Hub -></p>
</a>
</td>
<td class="corner-td" align="right"></td>
@@ -382,7 +382,7 @@ The camera itself is broken, damaged or in the worst case tampered. Have a look
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+8 -8
View File
@@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -150,7 +150,7 @@
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -160,7 +160,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -275,8 +275,8 @@
letter-spacing: 0em;
text-align: left;
color: #6D6666;
margin-top: 12px;">The daily limit of your account was reached (<b>{{dataUsage}} GB</b>), therefore we have disabled your account. This means that no new recordings will be uploaded to your Kerberos Hub agents until tomorrow.
Tomorrow your account will be reset, and recordings will be uploaded again to your Kerberos Hub account.</p>
margin-top: 12px;">The daily limit of your account was reached (<b>{{dataUsage}} GB</b>), therefore we have disabled your account. This means that no new recordings will be uploaded to your Hub agents until tomorrow.
Tomorrow your account will be reset, and recordings will be uploaded again to your Hub account.</p>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -286,7 +286,7 @@ Tomorrow your account will be reset, and recordings will be uploaded again to yo
letter-spacing: 0em;
text-align: left;
color: #6D6666;
margin-top: 12px;">If you are hitting your daily limits a lot, you might consider upgrading your Kerberos Hub subscription, or fine-tune your Kerberos Agents so they record less recordings.</p>
margin-top: 12px;">If you are hitting your daily limits a lot, you might consider upgrading your Hub subscription, or fine-tune your Agents so they record less recordings.</p>
<a style="text-decoration: none;color: none;" href="{{link}}">
<p style="font-family: Inter;
@@ -305,7 +305,7 @@ Tomorrow your account will be reset, and recordings will be uploaded again to yo
width: 160px;
border-radius: 4px;
text-align: center;
cursor: pointer;">Go to Kerberos Hub -></p>
cursor: pointer;">Go to Hub -></p>
</a>
</td>
<td class="corner-td" align="right"></td>
@@ -392,7 +392,7 @@ Tomorrow your account will be reset, and recordings will be uploaded again to yo
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+5 -5
View File
@@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -150,7 +150,7 @@
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -160,7 +160,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -224,7 +224,7 @@
padding-top: 12px;
padding-bottom: 24px;
padding-left: 0;
padding-right: 0;">You forgot your Kerberos Hub password!</h2>
padding-right: 0;">You forgot your Hub password!</h2>
<p style="font-family: Inter;
font-size: 14px;
@@ -368,7 +368,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+7 -7
View File
@@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -150,7 +150,7 @@
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -160,7 +160,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -225,7 +225,7 @@
margin-bottom: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;">High upload detected by one or more Kerberos Agents</h2>
padding-right: 0;">High upload detected by one or more Agents</h2>
</td>
<td class="corner-td" align="right"></td>
</tr>
@@ -262,7 +262,7 @@
text-align: left;
color: #6D6666;
margin-top: 12px;">You receive this alert as a lot of recordings have been uploaded during the last x minutes. This alert might indicate something is happening unusual, please have a look.
If you believe this is a false positive go to the alerts section of your Kerberos Hub account and update the setting accordingly.</p>
If you believe this is a false positive go to the alerts section of your Hub account and update the setting accordingly.</p>
<a style="text-decoration: none;color: none;" href="{{link}}">
@@ -282,7 +282,7 @@ If you believe this is a false positive go to the alerts section of your Kerbero
width: 160px;
border-radius: 4px;
text-align: center;
cursor: pointer;">Go to Kerberos Hub -></p>
cursor: pointer;">Go to Hub -></p>
</a>
</td>
<td class="corner-td" align="right"></td>
@@ -369,7 +369,7 @@ If you believe this is a false positive go to the alerts section of your Kerbero
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+5 -5
View File
@@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -150,7 +150,7 @@
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -160,7 +160,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -261,7 +261,7 @@
letter-spacing: 0em;
text-align: left;
color: #6D6666;
margin-top: 12px;">Someone, we hope it was you ({{ipaddress}}), signed in to your account. If this was not you, please go to Kerberos Hub and change your password ASAP.</p>
margin-top: 12px;">Someone, we hope it was you ({{ipaddress}}), signed in to your account. If this was not you, please go to Hub and change your password ASAP.</p>
<a style="text-decoration: none;color: none;" href="{{link}}">
<p style="font-family: Inter;
@@ -367,7 +367,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+4 -4
View File
@@ -8,7 +8,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -151,7 +151,7 @@
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -161,7 +161,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -369,7 +369,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+2 -2
View File
@@ -1,4 +1,4 @@
Kerberos.io
Hub
------------
We've something interesting for you
@@ -14,6 +14,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+418
View File
@@ -0,0 +1,418 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("https://kerberos.io/dist/fonts/Inter-Regular.woff?v=/dist/fonts/Inter-Regular.woff2?v=3.183.18") format("woff2"),
url("https://kerberos.io/dist/fonts/Inter-Regular.woff?v=/dist/fonts/Inter-Regular.woff2?v=3.183.18") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("https://kerberos.io/dist/fonts/Inter-Medium.woff2?v=3.18") format("woff2"),
url("https://kerberos.io/dist/fonts/Inter-Medium.woff?v=3.18") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("https://kerberos.io/dist/fonts/Inter-SemiBold.woff2?v=3.18") format("woff2"),
url("https://kerberos.io/dist/fonts/Inter-SemiBold.woff?v=3.18") format("woff");
}
@font-face {
font-family: 'Inter var';
font-weight: 100 900;
font-display: swap;
font-style: normal;
font-named-instance: 'Regular';
src: url("https://kerberos.io/dist/fonts/Inter-roman.var.woff2?v=3.18") format("woff2");
}
body{
background: #E5E5E5;
margin-top:0;
margin-bottom: 0;
margin-right: 0;
margin-left: 0;
padding-top: 0;
padding-left: 0;
padding-right: 0;
padding-bottom: 0;
font-family: 'Inter';
}
a, a:hover, a:active {
color: #262424;
text-decoration: none;
}
.corner-td{
width: 60px;
}
table {border-collapse:separate;max-width: 850px; margin: 0 auto; width: 100%;}
.ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td {line-height: 100%;}
.ExternalClass {width: 100%;}
@media screen and (max-width:500px){
.tab-td{
padding-left: 10px!important;
}
.tab-td a h4{
font-size: 14px!important;
}
.corner-td{
width: 20px!important;
}
.company-name-td h3{
font-size: 16px!important;
}
table.header-table{
padding-top: 8px!important;
padding-right: 0px!important;
padding-bottom: 24px!important;
padding-left: 0px!important;
}
.colored-card-td h4{
font-size: 14px!important;
}
.colored-card-td h2{
font-size: 20px!important;
}
.colored-card-td a p{
font-size: 12px!important;
width: 143px!important;
}
.colored-card-td{
padding-top: 24px!important;
padding-right: 24px!important;
padding-bottom: 24px!important;
padding-left: 24px!important;
}
.colorless-card-td{
padding-top: 24px!important;
padding-right: 24px!important;
padding-bottom: 24px!important;
padding-left: 24px!important;
}
.colorless-card-td h3{
font-size: 18px!important;
}
.colorless-card-td p{
font-size: 14px!important;
}
.colorless-card-table{
margin-left: 0px!important;
margin-right: 0px!important;
margin-top: 24px!important;
margin-bottom: 24px!important;
}
.footer-td{
display: table-row!important;
}
}
@media screen and (max-width:600px) {
.footer-td{
display: table-row!important;
}
}
@media screen and (max-width:650px) {
.footer-table{
margin-left: 0px!important;
margin-right: 0px!important;
margin-top: 0px!important;
margin-bottom: 36px!important;
}
}
</style>
</head>
<body height="100%" width="100%">
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tr>
<td bgcolor="E5E5E5">
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="36" class="header-table" style="padding-top: 36px ;padding-right: 0;padding-bottom: 36px;padding-left: 0;border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 24px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
<h4 style="font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 36px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: right;
color: #6D6666;">{{tab1_title}}</h4>
</a>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right" >
<a style="text-decoration: none;color: none;" href={{tab2_href}}>
<h4 style="font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 36px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: right;
color: #6D6666;">{{tab2_title}}</h4>
</a>
</td>
<td class="corner-td" align="right"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tr>
<td bgcolor="E5E5E5">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;" >
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td class="colored-card-td" bgcolor="#57356B" style="padding-left: 48px;padding-right: 48px;padding-top: 48px;padding-bottom: 48px;border-radius: 4px;background-color:#57356B;">
<h2 style=" font-family: Inter;
font-size: 24px;
font-style: normal;
font-weight: 600;
line-height: 36px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#FFFFFF;
padding-top: 12px;
margin-bottom: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;">A case has been shared with you</h2>
<h4 style="font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#b09fb9;">{{user}} shared a case with you</h4>
</td>
<td class="corner-td" align="right"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tr>
<td bgcolor="E5E5E5">
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="colorless-card-table" style="margin-top: 36px;margin-bottom: 36px;margin-left: 0;margin-right: 0;border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;" >
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td class="colorless-card-td" bgcolor="#FFFFFF" style="background-color:#FFFFFF;padding-top: 36px;padding-right: 43px;padding-bottom: 25px;padding-left: 43px;border-radius: 4px;">
<h3 style=" font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 36px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;
width: 280px">Open the shared case</h3>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #6D6666;
margin-top: 12px;">{{user}} has shared a case with you. Click the button below to open it. You'll be asked to request a one-time verification code from the share page itself.<br/><br/>This link will expire in {{expiry}}.</p>
<a style="text-decoration: none;color: none;" href="{{url}}">
<p style="font-family: Inter;
font-size: 14px;
font-style: normal;
line-height: 24px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#FFFFFF;
background-color: #84559F;
padding-top: 6px;
padding-bottom: 6px;
padding-right: 16px;
padding-left: 16px;
width: 130px;
border-radius: 4px;
text-align: center;
cursor: pointer;">Open case -></p>
</a>
</td>
<td class="corner-td" align="right"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tr>
<td bgcolor="E5E5E5">
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="footer-table" style="margin-top: 0;border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;" >
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;display:table-row !important">
<![endif]-->
<td height="146" width="190" class="footer-td" style="margin-bottom: 12px;" valign="top" align="left">
<table style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tbody>
<tr>
<td>
<h4 style=" font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 36px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">Get in touch</h4>
<a style="text-decoration: none;color: none;" href="mailto:support@kerberos.io">
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #A69D9D;">support@kerberos.io</p>
</a>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #A69D9D;">9000 Ghent, BE</p>
<a style="text-decoration: none;color: none;" href="https://kerberos.io/">
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #A69D9D;">https://kerberos.io</p>
</a>
</td>
</tr>
</tbody>
</table>
</td>
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;display:table-row !important">
<![endif]-->
<td class="footer-td" style="border-radius: 4px;padding-left: 0;padding-right: 0;padding-top: 0;padding-bottom: 0; margin-bottom: 12px;" valign="top" align="left">
<table style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tbody>
<tr>
<td>
<h4 style=" font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 36px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px;
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #A69D9D;">Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.</p>
<p style="margin-top: 12px;">
<a href="https://twitter.com/kerberosio" style="text-decoration: none;color: none;">
<img width="24" height="24" alt="Twitter" src="https://kerberos.io/images/email/twitter.png"/>
</a>
<a href="https://reddit.com/r/kerberos_io" style="text-decoration: none;color: none;">
<img g width="24" height="24" alt="Reddit" src="https://kerberos.io/images/email/reddit.png"/>
</a>
<a href="https://www.youtube.com/channel/UCnd9q7iRNNw4W95eQwQuECA" style="text-decoration: none;color: none;">
<img g width="24" height="24" alt="Youtube" src="https://kerberos.io/images/email/youtube.png"/>
</a>
<a href="https://github.com/kerberos-io" style="text-decoration: none;color: none;">
<img g width="24" height="24" alt="Github" src="https://kerberos.io/images/email/github.png"/>
</a>
</p>
</td>
</tr>
</tbody>
</table>
</td>
<td class="corner-td" align="right"></td>
</tr>
</tbody>
</table>
<table style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
<tbody>
<tr style="height: 50px">
<td></td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
Hub
------------
A case has been shared with you
{{user}} shared a case with you
Open the shared case
{{user}} has shared a case with you. Open the link below to access it — you'll be asked to request a one-time verification code from the share page.
{{url}}
This link will expire in {{expiry}}.
Get in touch
------------
support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+7 -7
View File
@@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<meta name="description" content="Kerberos.io Mailing">
<meta name="description" content="Hub notifications">
<style type="text/css">
@font-face {
@@ -150,7 +150,7 @@
<tbody>
<tr>
<td class="corner-td" align="left"></td>
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td width="48" height="36" align="left"><img alt="Hub" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
<td height="36" align="left" class="company-name-td">
<h3 width="36" height="36" style=" font-family: Inter;
font-size: 20px;
@@ -160,7 +160,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color: #262424;">Kerberos.io</h3>
color: #262424;">Hub</h3>
</td>
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
@@ -224,7 +224,7 @@
padding-top: 12px;
padding-bottom: 24px;
padding-left: 0;
padding-right: 0;">Welcome to Kerberos Hub</h2>
padding-right: 0;">Welcome to Hub</h2>
<a style="text-decoration: none;color: none;" href="{{link}}">
<p style="font-family: Inter;
font-size: 14px;
@@ -268,7 +268,7 @@
letter-spacing: 0em;
text-align: left;
color: #262424;
width: 280px">Kerberos Hub in a nutshell</h3>
width: 280px">Hub in a nutshell</h3>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -278,7 +278,7 @@
letter-spacing: 0em;
text-align: left;
color: #6D6666;
margin-top: 12px;">With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.</p>
margin-top: 12px;">With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.</p>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -374,7 +374,7 @@
mso-line-height-rule:exactly;
letter-spacing: 0em;
text-align: left;
color:#6D6666;">About Kerberos</h4>
color:#6D6666;">About the ecosystem</h4>
<p style=" font-family: Inter;
font-size: 14px;
font-style: normal;
+5 -5
View File
@@ -1,13 +1,13 @@
Kerberos.io
Hub
------------
Hey, {{user}}
Welcome to Kerberos Hub
Welcome to Hub
Activate your account ->
{{link}}
Kerberos Hub in a nutshell
With Kerberos Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
Hub in a nutshell
With Hub you can access your surveillance media remotely. By subscribing to a plan, you get access to a set of features, from advanced filtering, notifications to machine learning.
However before you get started this amazing applications, please activate your account.
@@ -17,6 +17,6 @@ support@kerberos.io
9000 Ghent, BE
https://kerberos.io
About Kerberos
About the ecosystem
------------
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
+2
View File
@@ -39,6 +39,8 @@ if [ "$command" == "apply" ]; then
# Adjust the base path reference
sed -i "s|../../base|./base|g" ./kustomization.yaml
bash scripts/install-garage.sh --prepare-only || exit 1
# Apply kustomize installation
kubectl kustomize ./ --enable-helm --load-restrictor LoadRestrictionsNone | kubectl apply -f -
+56
View File
@@ -1 +1,57 @@
# Amazon DocumentDB
[Amazon DocumentDB](https://aws.amazon.com/documentdb/) is a managed, MongoDB
compatible database. It can be used as the metadata store for Hub
instead of a self-hosted MongoDB.
## Things to know
- **Not reachable from outside its VPC.** DocumentDB has no public endpoint, so
the Hub services must run inside (or be peered with) the same VPC.
- **TLS is enabled by default.** Clients must trust the Amazon RDS certificate
authority bundle:
`https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem`.
- **Not every MongoDB feature is available.** Retryable writes, the MongoDB
Stable API, geospatial queries/indexes and complex `$lookup` pipelines are
unsupported. Set `mongodb.flavor: "documentdb"` and
`mongodb.retryWrites: "false"` in the hub chart so those code paths are
disabled.
## Provisioning
The [`amazon-eks-documentdb`](../amazon-eks-documentdb/README.md) Terraform
stack creates a VPC, an EKS cluster and a DocumentDB cluster with TLS enforced,
and outputs a ready to paste `mongodb` values block for the hub helm chart.
## Connecting Hub
Configure the database through `mongodb.uri` (not `mongodb.host`) and point the
chart at the CA bundle:
```bash
curl -O https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
kubectl create secret generic mongodb-ca --from-file=global-bundle.pem -n kerberos-hub
```
```yaml
mongodb:
flavor: "documentdb"
retryWrites: "false"
uri: "mongodb://<user>:<password>@<cluster>.docdb.amazonaws.com:27017/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false"
adminDatabase: "admin"
authenticationMechanism: "SCRAM-SHA-1"
tls:
enabled: true
existingSecret: "mongodb-ca"
caFileName: "global-bundle.pem"
mountPath: "/certs"
```
The chart mounts the bundle read-only into every workload that talks to
MongoDB and appends `tls=true&tlsCAFile=/certs/global-bundle.pem` to the
connection string.
## Related
- [`../amazon-eks-documentdb`](../amazon-eks-documentdb/README.md) — Terraform for EKS + DocumentDB
- [`../../overlays/documentdb`](../../overlays/documentdb) — Kustomize overlay using DocumentDB
+13
View File
@@ -0,0 +1,13 @@
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.*
*.tfplan
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
terraform.tfvars
*.auto.tfvars
hub-values.yaml
+375
View File
@@ -0,0 +1,375 @@
# Amazon EKS + DocumentDB (Terraform)
Terraform stack that creates a **basic Kubernetes cluster (EKS) and a managed
MongoDB-compatible database (Amazon DocumentDB) on AWS**, wired together so
Hub can be installed on it straight away.
It is primarily meant as a **reproducible test environment** for the DocumentDB
support in the [`hub` helm chart](https://github.com/kerberos-io/helm-charts),
in particular the `mongodb.tls.*` values that mount the Amazon RDS certificate
authority bundle. It is deliberately small and cheap, not a hardened production
landing zone.
## What it creates
```mermaid
flowchart LR
subgraph VPC["VPC (10.20.0.0/16)"]
subgraph Public["Public subnets"]
NAT[NAT gateway]
LB[Load balancers]
end
subgraph Private["Private subnets"]
NODES[EKS managed node group]
DOCDB[(DocumentDB cluster<br/>TLS enforced)]
end
end
EKSCP[EKS control plane] --- NODES
NODES -- "27017 / TLS" --> DOCDB
NODES --> NAT
```
| Component | Details |
| --------- | ------- |
| VPC | Public + private subnets across 3 availability zones, internet gateway, NAT gateway |
| EKS | Managed control plane, one managed node group, `coredns`, `kube-proxy`, `vpc-cni`, `eks-pod-identity-agent` and `aws-ebs-csi-driver` add-ons (IRSA role included) |
| DocumentDB | Cluster + instances in the private subnets, encryption **at rest** (KMS) and **in transit** (`tls=enabled`), subnet group, cluster parameter group |
| Security | A dedicated security group that only allows port `27017` from the EKS worker node security group (plus any extra CIDRs you pass in) |
> [!IMPORTANT]
> DocumentDB has **no public endpoint**. It can only be reached from inside the
> VPC, which is why the workloads that talk to it must run on this cluster (or
> you must tunnel through a bastion host / VPN).
> [!WARNING]
> This stack costs money while it exists (EKS control plane, NAT gateway, EC2
> nodes, DocumentDB instances and storage). Run `terraform destroy` when you are
> done.
## Prerequisites
- [Terraform](https://developer.hashicorp.com/terraform/downloads) >= 1.5
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) v2, authenticated with permissions to create VPC, EKS, IAM and DocumentDB resources
- `kubectl` and `helm`
## Usage
```bash
cd deployment/modules/amazon-eks-documentdb
cp terraform.tfvars.example terraform.tfvars
$EDITOR terraform.tfvars
terraform init
terraform plan
terraform apply
```
Creating the cluster and the database takes a while (EKS and DocumentDB are
both slow to provision).
### Replacing the VPC
AWS cannot move a DocumentDB subnet group or cluster between VPCs. The subnet
group name therefore includes the VPC ID, allowing Terraform to create a new
group and replace the cluster when the VPC changes instead of attempting an
unsupported in-place subnet update.
Discard any saved plan created before a VPC replacement or configuration
change, then create and apply a fresh one:
```bash
rm -f tfplan
terraform plan -out=tfplan
terraform apply tfplan
```
> [!WARNING]
> Replacing the VPC also replaces the DocumentDB cluster. If it contains data,
> create and verify a snapshot before applying the plan; a final snapshot
> preserves the old data but is not restored into the replacement cluster
> automatically.
State is kept locally by default. For anything shared, add a backend, for
example:
```hcl
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "kerberos-hub/eks-documentdb.tfstate"
region = "eu-west-1"
}
}
```
### Connect kubectl
```bash
$(terraform output -raw update_kubeconfig_command)
kubectl get nodes
```
## Installing Hub against DocumentDB
### 1. Create the certificate authority secret
DocumentDB presents a certificate signed by the Amazon RDS certificate
authority, so every client needs the bundle:
```bash
kubectl create namespace kerberos-hub
curl -O https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
kubectl create secret generic mongodb-ca \
--from-file=global-bundle.pem \
-n kerberos-hub
```
### 2. Generate the values
```bash
terraform output -raw hub_values_snippet > hub-documentdb-values.yaml
```
Which produces something like:
```yaml
mongodb:
flavor: "documentdb"
retryWrites: "false"
uri: "mongodb://kerberos:...@kerberos-hub-docdb.cluster-xxxx.eu-west-1.docdb.amazonaws.com:27017/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false"
adminDatabase: "admin"
authenticationMechanism: "SCRAM-SHA-1"
tls:
enabled: true
existingSecret: "mongodb-ca"
caFileName: "global-bundle.pem"
mountPath: "/certs"
```
The chart mounts the bundle into every workload that talks to MongoDB, appends
`tls=true&tlsCAFile=/certs/global-bundle.pem` to the URI, and exposes
`MONGODB_TLS`, `MONGODB_TLS_CA_FILE` and `MONGODB_TLS_INSECURE_SKIP_VERIFY`
through the `mongodb-config` ConfigMap.
> [!NOTE]
> With DocumentDB you must configure the database through `mongodb.uri`, not
> through `mongodb.host` / `mongodb.username` / `mongodb.password`, so that the
> TLS parameters end up in the connection string that every service uses.
### 3. Install the chart
```bash
helm repo add kerberos https://charts.kerberos.io
helm install hub kerberos/hub \
--version 0.127.0 \
-n kerberos-hub \
-f your-hub-values.yaml \
-f hub-documentdb-values.yaml \
--set-string kerberoshub.api.tag=v1.9.49
```
Hub API `v1.9.49` is the first release that disables the MongoDB Stable API in
DocumentDB mode. Older images can fail the SCRAM-SHA-1 handshake with
`sasl conversation error: ... Query syntax error`, even when the username,
password and authentication mechanism are correct.
The `hub_values_snippet` output contains credentials, so treat the generated
file as a secret and do not commit it.
### 4. Verify
```bash
kubectl logs -n kerberos-hub deploy/hub-api | head -50
kubectl exec -n kerberos-hub deploy/hub-api -- ls -l /certs
kubectl get deployment hub-api -n kerberos-hub \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
kubectl get configmap mongodb-config -n kerberos-hub \
-o jsonpath='{.data.MONGODB_FLAVOR}{"\n"}'
```
The last two commands must report Hub API `v1.9.49` or newer and
`documentdb`, respectively.
A one-off connectivity check from inside the cluster:
```bash
kubectl run mongosh --rm -it --restart=Never -n kerberos-hub \
--image=mongodb/mongodb-community-server:7.0-ubi8 \
--overrides='{"spec":{"volumes":[{"name":"ca","secret":{"secretName":"mongodb-ca"}}],"containers":[{"name":"mongosh","image":"mongodb/mongodb-community-server:7.0-ubi8","stdin":true,"tty":true,"command":["mongosh"],"args":["'"$(terraform output -raw mongodb_uri)"'&tls=true&tlsCAFile=/certs/global-bundle.pem"],"volumeMounts":[{"name":"ca","mountPath":"/certs"}]}]}}'
```
### 5. Import the example Hub data
This module has a separate DocumentDB import under [`database-import`](database-import).
It uses the chart-managed `mongodb-config`, mounts `mongodb-ca`, forces TLS with
the Amazon RDS CA bundle, and refuses to run unless the backend flavor is
`documentdb` with retryable writes disabled. Install Hub chart `0.127.0` or
newer before running it.
Run it after installing Hub:
```bash
./database-import/run.sh
```
The import is idempotent: it upserts two example users, one subscription and
five settings documents using fixed IDs, then verifies those records. It can be
rerun after deleting or replacing the DocumentDB cluster.
| Account | Password | Role |
| ------- | -------- | ---- |
| `example-user` | `example-password` | Hub owner |
| `example-application` | `example-password` | Admin application |
These are public example credentials. Do not use this seed data in a production
deployment.
## Public HTTPS ingress
The [`ingress`](ingress) package installs ingress-nginx behind an
internet-facing AWS Network Load Balancer, installs cert-manager, and creates
Let's Encrypt certificates for exactly these routes:
| Host | Service |
| ---- | ------- |
| `aws-app.kerberos.lol` | `hub-frontend-svc:80` |
| `aws-api.kerberos.lol` | `hub-api-svc:8081` |
Keep the Hub chart's global `ingress` value disabled. The module owns these two
Ingress resources so that enabling public access does not also expose the Hub
administration services.
Install the controllers and resources:
```bash
./ingress/install.sh
```
The cert-manager values use `1.1.1.1` and `8.8.8.8` for HTTP-01 self-checks.
This avoids waiting for the AWS VPC resolver if it cached an `NXDOMAIN` before
the public records were created. The override affects only cert-manager's ACME
self-checks; normal cluster DNS continues to use the VPC resolver.
The script prints the NLB hostname. Create both DNS records as CNAMEs pointing
to that hostname. You can retrieve it again with:
```bash
kubectl get service ingress-nginx-controller \
--namespace ingress-nginx \
--output jsonpath='{.status.loadBalancer.ingress[0].hostname}{"\n"}'
```
| DNS name | Type | Target |
| -------- | ---- | ------ |
| `aws-app.kerberos.lol` | CNAME | The ingress-nginx NLB hostname |
| `aws-api.kerberos.lol` | CNAME | The ingress-nginx NLB hostname |
cert-manager automatically retries its HTTP-01 challenges after DNS resolves;
do not delete pending CertificateRequests. Wait for both certificates:
```bash
kubectl wait --namespace kerberos-hub \
--for=condition=Ready certificate/aws-app-kerberos-lol-tls \
certificate/aws-api-kerberos-lol-tls \
--timeout=10m
```
Set Hub's public URLs with the non-secret values fragment after the certificates
are ready. Include the same private Hub and DocumentDB values used for the
original installation:
```bash
helm upgrade hub kerberos/hub \
--version 0.127.0 \
--namespace kerberos-hub \
--file your-hub-values.yaml \
--file hub-documentdb-values.yaml \
--file ingress/hub-public-values.yaml \
--set-string kerberoshub.api.tag=v1.9.49 \
--atomic \
--wait
```
Verify both public endpoints:
```bash
curl --fail https://aws-api.kerberos.lol/health
curl --fail --output /dev/null https://aws-app.kerberos.lol/login
```
## Persistent volumes
The EBS CSI driver is installed, but EKS ships `gp2` as the default storage
class. To use `gp3` instead:
```bash
kubectl patch storageclass gp2 -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl apply -f - <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: gp3
EOF
```
## Tear down
```bash
# Remove Kubernetes resources first so AWS load balancers and volumes are cleaned up.
kubectl delete -f ingress/hub-ingresses.yaml --ignore-not-found
helm uninstall hub -n kerberos-hub
kubectl delete -f ingress/cluster-issuer.yaml --ignore-not-found
helm uninstall cert-manager -n cert-manager
helm uninstall ingress-nginx -n ingress-nginx
terraform destroy
```
## Inputs
The defaults are tuned for a small test stack. See [variables.tf](variables.tf)
for the full list; the ones you are most likely to change:
| Variable | Default | Description |
| -------- | ------- | ----------- |
| `name` | `kerberos-hub` | Name prefix for every resource |
| `region` | `eu-west-1` | AWS region |
| `vpc_cidr` | `10.20.0.0/16` | VPC CIDR block |
| `single_nat_gateway` | `true` | One shared NAT gateway (cheaper, not highly available) |
| `kubernetes_version` | `1.31` | EKS control plane version |
| `cluster_endpoint_public_access_cidrs` | `["0.0.0.0/0"]` | Who may reach the Kubernetes API, **narrow this down** |
| `node_instance_types` | `["t3.large"]` | Worker node instance types |
| `node_desired_size` | `2` | Number of worker nodes |
| `docdb_instance_class` | `db.t3.medium` | DocumentDB instance class |
| `docdb_instance_count` | `1` | Number of DocumentDB instances |
| `docdb_username` | `kerberos` | Master username |
| `docdb_password` | generated | Master password, generated when unset |
| `docdb_tls` | `true` | Enforce TLS on the cluster |
| `docdb_allowed_cidrs` | `[]` | Extra CIDRs allowed on port 27017 |
## Outputs
| Output | Description |
| ------ | ----------- |
| `cluster_name`, `cluster_endpoint` | EKS cluster identity |
| `update_kubeconfig_command` | Ready to run `aws eks update-kubeconfig ...` |
| `vpc_id`, `private_subnet_ids` | Networking identifiers |
| `docdb_endpoint`, `docdb_reader_endpoint`, `docdb_port` | DocumentDB connection details |
| `docdb_username`, `docdb_password` | Master credentials (password is sensitive) |
| `mongodb_uri` | Connection string for `mongodb.uri` (sensitive) |
| `hub_values_snippet` | Ready to paste helm values including the TLS block (sensitive) |
## Related
- [`../amazon-documentdb`](../amazon-documentdb/README.md) — using DocumentDB as the Hub metadata store
- [`../../overlays/documentdb`](../../overlays/documentdb) — Kustomize overlay that deploys Hub against DocumentDB
- [`../../README.k8s-managed.md`](../../README.k8s-managed.md) — installing on managed Kubernetes
@@ -0,0 +1,340 @@
const database = db.getSiblingDB('Kerberos');
const ownerUsername = 'example-user';
const applicationUsername = 'example-application';
const organisationName = 'example-orgranisation';
const zeroObjectId = ObjectId('000000000000000000000000');
const ownerCreatedAt = ISODate('2016-09-20T09:27:58.811Z');
const ownerUpdatedAt = ISODate('2020-06-14T05:01:35.000Z');
function upsert(collection, id, values) {
const objectId = typeof id === 'string' ? ObjectId(id) : id;
const result = database.getCollection(collection).updateOne(
{ _id: objectId },
{ $set: values },
{ upsert: true },
);
if (!result.acknowledged) {
throw new Error(`Upsert was not acknowledged for ${collection}/${id}`);
}
}
function resolveUserId(username) {
const users = database.users.find(
{ username },
{ _id: 1 },
).limit(2).toArray();
if (users.length > 1) {
throw new Error(`Cannot import duplicate username: ${username}`);
}
if (users.length === 1) {
if (!users[0]._id || typeof users[0]._id.toHexString !== 'function') {
throw new Error(`Existing user ${username} does not have an ObjectId _id`);
}
return users[0]._id;
}
return ObjectId();
}
function insertIfMissing(collection, id, values) {
const result = database.getCollection(collection).updateOne(
{ _id: id },
{ $setOnInsert: values },
{ upsert: true },
);
if (!result.acknowledged) {
throw new Error(`Insert was not acknowledged for ${collection}/${id}`);
}
}
function setObjectIdIfMissing(collection, id, field, value) {
const result = database.getCollection(collection).updateOne(
{
_id: id,
$or: [
{ [field]: { $exists: false } },
{ [field]: null },
{ [field]: zeroObjectId },
],
},
{ $set: { [field]: value } },
);
if (!result.acknowledged) {
throw new Error(`Initialisation was not acknowledged for ${collection}.${field}`);
}
}
const ownerId = resolveUserId(ownerUsername);
const applicationId = resolveUserId(applicationUsername);
// Login: example-user / example-password
upsert('users', ownerId, {
username: ownerUsername,
email: 'example-user@email.com',
password: '$2a$10$jwLcD/.UT/1WLK7ct1XuHewI3GQXwW3zerPhCCs7QDrReEuIHbVYi',
role: 'owner',
google2fa_enabled: false,
timezone: 'Europe/Brussels',
isActive: NumberLong('1'),
registerToken: '',
updated_at: ISODate('2020-06-14T05:01:35.000Z'),
created_at: ISODate('2016-09-20T09:27:58.811Z'),
amazon_secret_access_key: 'K6rRLBI1xxxCk3C1H',
amazon_access_key_id: 'AKIAxxxxxxG5Q',
card_brand: 'Visa',
card_last_four: '0000',
card_status: 'ok',
card_status_message: null,
});
insertIfMissing('organisation', ownerId, {
name: organisationName,
ownerId,
isActive: true,
settings: {
timezone: 'Europe/Brussels',
},
audit: {
createdBy: ownerId.toHexString(),
createdAt: ownerCreatedAt,
updatedBy: ownerId.toHexString(),
updatedAt: ownerUpdatedAt,
lastAction: 'organisation.created',
},
});
const membershipResult = database.organisation_users.updateOne(
{
userId: ownerId,
organisationId: ownerId,
},
{
$setOnInsert: {
userId: ownerId,
organisationId: ownerId,
status: 'active',
invitedBy: ownerId,
joinedAt: ownerCreatedAt,
audit: {
createdBy: ownerId.toHexString(),
createdAt: ownerCreatedAt,
updatedBy: ownerId.toHexString(),
updatedAt: ownerUpdatedAt,
lastAction: 'organisation.membership.created',
},
},
},
{ upsert: true },
);
if (!membershipResult.acknowledged) {
throw new Error('Owner organisation membership was not acknowledged');
}
insertIfMissing('project', ownerId, {
organisationId: ownerId,
name: 'Project 1',
slug: 'project-1',
isActive: true,
audit: {
createdBy: ownerId.toHexString(),
createdAt: ownerCreatedAt,
updatedBy: ownerId.toHexString(),
updatedAt: ownerUpdatedAt,
lastAction: 'project.default.created',
},
});
setObjectIdIfMissing('users', ownerId, 'organisationId', ownerId);
const importedOwner = database.users.findOne(
{ _id: ownerId, organisationId: { $type: 'objectId' } },
{ organisationId: 1 },
);
if (!importedOwner) {
throw new Error('Imported owner has an invalid organisationId');
}
setObjectIdIfMissing('users', ownerId, 'projectId', importedOwner.organisationId);
// Admin login: example-application / example-password
upsert('users', applicationId, {
username: applicationUsername,
email: 'example-application@email.com',
password: '$2a$10$jwLcD/.UT/1WLK7ct1XuHewI3GQXwW3zerPhCCs7QDrReEuIHbVYi',
role: 'application',
google2fa_enabled: false,
timezone: 'Europe/Brussels',
isActive: NumberLong('1'),
registerToken: '',
updated_at: ISODate('2020-06-14T05:01:35.000Z'),
created_at: ISODate('2016-09-20T09:27:58.811Z'),
});
upsert('subscriptions', ownerId, {
name: 'default',
stripe_id: 'sub_9ECyjjMz3R7etK',
stripe_plan: 'enterprise',
quantity: 1,
trial_ends_at: null,
ends_at: null,
user_id: ownerId.toHexString(),
status: 'active',
updated_at: ISODate('2021-04-27T09:45:30.169Z'),
created_at: ISODate('2016-09-20T09:35:03.448Z'),
stripe_status: 'active',
});
setObjectIdIfMissing('subscriptions', ownerId, 'organisation_id', ownerId);
upsert('settings', '5a72c509e17699d18ada9154', {
key: 'plan',
map: {
basic: {
level: NumberInt(1),
uploadLimit: NumberInt(100),
videoLimit: NumberInt(100),
usage: NumberInt(500),
analysisLimit: NumberInt(0),
dayLimit: NumberInt(3),
},
premium: {
level: NumberInt(2),
uploadLimit: NumberInt(500),
videoLimit: NumberInt(500),
usage: NumberInt(1000),
analysisLimit: NumberInt(0),
dayLimit: NumberInt(7),
},
gold: {
level: NumberInt(3),
uploadLimit: NumberInt(1000),
videoLimit: NumberInt(1000),
usage: NumberInt(3000),
analysisLimit: NumberInt(1000),
dayLimit: NumberInt(30),
},
business: {
level: NumberInt(4),
uploadLimit: NumberInt(99999999),
videoLimit: NumberInt(99999999),
usage: NumberInt(10000),
analysisLimit: NumberInt(1000),
dayLimit: NumberInt(30),
},
enterprise: {
level: NumberInt(5),
uploadLimit: NumberInt(99999999),
videoLimit: NumberInt(99999999),
usage: NumberInt(99999999),
analysisLimit: NumberInt(5000),
dayLimit: NumberInt(30),
},
},
});
upsert('settings', '63f346ec64011a574161cf99', {
key: 'classifications',
map: {
objects: [
{ text: 'Car', value: 'car', icon: 'car' },
{ text: 'Person', value: 'pedestrian', icon: 'pedestrian' },
],
},
});
upsert('settings', '5a43fa12d885eb7da57046b3', {
key: 'sequence',
map: { timeBetween: NumberInt(60) },
});
upsert('settings', '5a4d3a6bd885eb7da5e6b297', {
key: 'throttler',
map: { waitingTime: NumberInt(60) },
});
upsert('settings', '5a53d0a0d885eb7da53ed5a6', {
key: 'analysis',
map: { waitingTime: NumberInt(15) },
});
const importedUsers = database.users.countDocuments({
_id: {
$in: [ownerId, applicationId],
},
});
const importedOwnerUsernames = database.users.countDocuments({ username: ownerUsername });
const importedApplicationUsernames = database.users.countDocuments({ username: applicationUsername });
const importedOwnerSelection = database.users.findOne(
{
_id: ownerId,
organisationId: { $type: 'objectId' },
projectId: { $type: 'objectId' },
},
{ organisationId: 1, projectId: 1 },
);
const importedOrganisations = database.organisation.countDocuments({
_id: ownerId,
ownerId,
isActive: true,
});
const importedMemberships = database.organisation_users.countDocuments({
userId: ownerId,
organisationId: ownerId,
status: 'active',
});
const importedProjects = database.project.countDocuments({
_id: ownerId,
organisationId: ownerId,
slug: 'project-1',
isActive: true,
});
const importedSubscriptions = database.subscriptions.countDocuments({
_id: ownerId,
organisation_id: ownerId,
});
const importedSettings = database.settings.countDocuments({
_id: {
$in: [
ObjectId('5a72c509e17699d18ada9154'),
ObjectId('63f346ec64011a574161cf99'),
ObjectId('5a43fa12d885eb7da57046b3'),
ObjectId('5a4d3a6bd885eb7da5e6b297'),
ObjectId('5a53d0a0d885eb7da53ed5a6'),
],
},
});
if (!importedOwnerSelection) {
throw new Error('Imported owner is missing ObjectId organisation/project selections');
}
const selectedOrganisationExists = database.organisation.countDocuments({
_id: importedOwnerSelection.organisationId,
});
const selectedProjectExists = database.project.countDocuments({
_id: importedOwnerSelection.projectId,
organisationId: importedOwnerSelection.organisationId,
});
const selectedProjectIsLogicalDefault = importedOwnerSelection.projectId.equals(
importedOwnerSelection.organisationId,
);
if (
importedUsers !== 2
|| importedOwnerUsernames !== 1
|| importedApplicationUsernames !== 1
|| importedOrganisations !== 1
|| importedMemberships !== 1
|| importedProjects !== 1
|| importedSubscriptions !== 1
|| importedSettings !== 5
|| selectedOrganisationExists !== 1
|| (!selectedProjectIsLogicalDefault && selectedProjectExists !== 1)
) {
throw new Error(
`Import verification failed: users=${importedUsers}, `
+ `organisations=${importedOrganisations}, memberships=${importedMemberships}, `
+ `projects=${importedProjects}, subscriptions=${importedSubscriptions}, `
+ `settings=${importedSettings}`,
);
}
print('Imported 2 users, 1 organisation, 1 membership, 1 project, 1 subscription, and 5 settings records into Kerberos.');
@@ -0,0 +1,81 @@
apiVersion: batch/v1
kind: Job
metadata:
name: hub-documentdb-import
labels:
app.kubernetes.io/name: hub-documentdb-import
spec:
activeDeadlineSeconds: 300
backoffLimit: 1
template:
metadata:
labels:
app.kubernetes.io/name: hub-documentdb-import
spec:
restartPolicy: Never
containers:
- name: import
image: mongo:7.0
imagePullPolicy: IfNotPresent
command:
- sh
- -ec
- |
flavor="$(printf '%s' "$MONGODB_FLAVOR" | tr '[:upper:]' '[:lower:]')"
retry_writes="$(printf '%s' "$MONGODB_RETRY_WRITES" | tr '[:upper:]' '[:lower:]')"
if [ "$flavor" != "documentdb" ]; then
echo "Refusing import: MONGODB_FLAVOR must be documentdb" >&2
exit 1
fi
if [ "$retry_writes" != "false" ]; then
echo "Refusing import: MONGODB_RETRY_WRITES must be false for DocumentDB" >&2
exit 1
fi
if [ ! -r /certs/global-bundle.pem ]; then
echo "Refusing import: /certs/global-bundle.pem is not readable" >&2
exit 1
fi
mongosh "$MONGODB_URI" \
--tls \
--tlsCAFile /certs/global-bundle.pem \
--quiet \
--file /scripts/hub-import.js
env:
- name: MONGODB_URI
valueFrom:
configMapKeyRef:
name: mongodb-config
key: MONGODB_URI
- name: MONGODB_FLAVOR
valueFrom:
configMapKeyRef:
name: mongodb-config
key: MONGODB_FLAVOR
- name: MONGODB_RETRY_WRITES
valueFrom:
configMapKeyRef:
name: mongodb-config
key: MONGODB_RETRY_WRITES
resources:
requests:
cpu: 10m
memory: 64Mi
volumeMounts:
- name: import-script
mountPath: /scripts
readOnly: true
- name: mongodb-tls
mountPath: /certs
readOnly: true
volumes:
- name: import-script
configMap:
name: hub-documentdb-import
- name: mongodb-tls
secret:
secretName: mongodb-ca
items:
- key: global-bundle.pem
path: global-bundle.pem
@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: hub-documentdb-import
files:
- hub-import.js
resources:
- job.yaml
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
namespace="${NAMESPACE:-kerberos-hub}"
timeout="${TIMEOUT:-5m}"
job_name="hub-documentdb-import"
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
for command_name in kubectl; do
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "Required command not found: $command_name" >&2
exit 1
fi
done
flavor="$(kubectl get configmap mongodb-config \
--namespace "$namespace" \
--output jsonpath='{.data.MONGODB_FLAVOR}')"
retry_writes="$(kubectl get configmap mongodb-config \
--namespace "$namespace" \
--output jsonpath='{.data.MONGODB_RETRY_WRITES}')"
ca_bundle="$(kubectl get secret mongodb-ca \
--namespace "$namespace" \
--output jsonpath='{.data.global-bundle\.pem}')"
if [[ "${flavor,,}" != "documentdb" ]]; then
echo "Refusing import: mongodb-config MONGODB_FLAVOR must be documentdb" >&2
exit 1
fi
if [[ "${retry_writes,,}" != "false" ]]; then
echo "Refusing import: mongodb-config MONGODB_RETRY_WRITES must be false" >&2
exit 1
fi
if [[ -z "$ca_bundle" ]]; then
echo "Refusing import: mongodb-ca/global-bundle.pem is missing" >&2
exit 1
fi
kubectl delete job "$job_name" \
--namespace "$namespace" \
--ignore-not-found=true \
--wait=true
kubectl apply --kustomize "$script_dir" --namespace "$namespace"
if ! kubectl wait \
--namespace "$namespace" \
--for=condition=complete \
--timeout="$timeout" \
"job/$job_name"; then
kubectl logs --namespace "$namespace" "job/$job_name" --all-containers=true || true
exit 1
fi
kubectl logs --namespace "$namespace" "job/$job_name" --all-containers=true
+131
View File
@@ -0,0 +1,131 @@
###############################################################################
# DocumentDB
#
# The cluster is created with TLS (encryption in transit) and encryption at
# rest enabled. Clients must trust the Amazon RDS certificate authority bundle:
#
# curl -O https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
#
# For Hub that bundle is mounted through the helm chart's
# `mongodb.tls` values, see the README next to this file.
###############################################################################
resource "random_password" "docdb" {
count = var.docdb_password == null ? 1 : 0
length = 32
special = true
# DocumentDB rejects '/', '"' and '@' in the master password. '@' and '/'
# would also break the MongoDB connection string.
override_special = "!#$%&*()-_=+[]{}<>:?"
}
locals {
docdb_password = var.docdb_password != null ? var.docdb_password : random_password.docdb[0].result
docdb_subnet_group_name = "${local.name}-docdb-${module.vpc.vpc_id}"
}
resource "aws_security_group" "docdb" {
name = "${local.name}-docdb"
description = "MongoDB wire protocol access to the Hub DocumentDB cluster"
vpc_id = module.vpc.vpc_id
tags = merge(local.tags, { Name = "${local.name}-docdb" })
}
resource "aws_vpc_security_group_ingress_rule" "docdb_from_eks_nodes" {
security_group_id = aws_security_group.docdb.id
description = "DocumentDB from the EKS worker nodes"
referenced_security_group_id = module.eks.node_security_group_id
ip_protocol = "tcp"
from_port = 27017
to_port = 27017
}
resource "aws_vpc_security_group_ingress_rule" "docdb_from_cidrs" {
for_each = toset(var.docdb_allowed_cidrs)
security_group_id = aws_security_group.docdb.id
description = "DocumentDB from ${each.value}"
cidr_ipv4 = each.value
ip_protocol = "tcp"
from_port = 27017
to_port = 27017
}
resource "aws_docdb_subnet_group" "this" {
name = local.docdb_subnet_group_name
description = "Private subnets of the Hub VPC"
subnet_ids = module.vpc.private_subnets
tags = local.tags
lifecycle {
create_before_destroy = true
}
}
resource "aws_docdb_cluster_parameter_group" "this" {
name = "${local.name}-docdb"
family = var.docdb_parameter_group_family
description = "Hub DocumentDB parameters"
parameter {
name = "tls"
value = var.docdb_tls ? "enabled" : "disabled"
}
tags = local.tags
lifecycle {
create_before_destroy = true
}
}
resource "aws_docdb_cluster" "this" {
cluster_identifier = "${local.name}-docdb"
engine = "docdb"
engine_version = var.docdb_engine_version
port = 27017
master_username = var.docdb_username
master_password = local.docdb_password
db_subnet_group_name = aws_docdb_subnet_group.this.name
db_cluster_parameter_group_name = aws_docdb_cluster_parameter_group.this.name
vpc_security_group_ids = [aws_security_group.docdb.id]
storage_encrypted = true
kms_key_id = var.docdb_kms_key_id
backup_retention_period = var.docdb_backup_retention_period
preferred_backup_window = "02:00-04:00"
preferred_maintenance_window = "sun:04:30-sun:05:30"
enabled_cloudwatch_logs_exports = var.docdb_enabled_cloudwatch_logs_exports
deletion_protection = var.docdb_deletion_protection
skip_final_snapshot = var.docdb_skip_final_snapshot
final_snapshot_identifier = var.docdb_skip_final_snapshot ? null : "${local.name}-docdb-final"
tags = local.tags
lifecycle {
replace_triggered_by = [aws_docdb_subnet_group.this.name]
}
}
resource "aws_docdb_cluster_instance" "this" {
count = var.docdb_instance_count
identifier = "${local.name}-docdb-${count.index}"
cluster_identifier = aws_docdb_cluster.this.id
instance_class = var.docdb_instance_class
auto_minor_version_upgrade = true
tags = local.tags
}
+70
View File
@@ -0,0 +1,70 @@
###############################################################################
# EKS
###############################################################################
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.31"
cluster_name = local.name
cluster_version = var.kubernetes_version
cluster_endpoint_public_access = var.cluster_endpoint_public_access
cluster_endpoint_public_access_cidrs = var.cluster_endpoint_public_access_cidrs
# Give the identity running `terraform apply` cluster-admin, so that
# `aws eks update-kubeconfig` immediately works.
enable_cluster_creator_admin_permissions = true
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
cluster_addons = {
coredns = {}
kube-proxy = {}
vpc-cni = {}
eks-pod-identity-agent = {}
aws-ebs-csi-driver = {
service_account_role_arn = module.ebs_csi_irsa.iam_role_arn
}
}
eks_managed_node_groups = {
default = {
instance_types = var.node_instance_types
capacity_type = "ON_DEMAND"
min_size = var.node_min_size
max_size = var.node_max_size
desired_size = var.node_desired_size
disk_size = var.node_disk_size
}
}
tags = local.tags
}
###############################################################################
# EBS CSI driver
#
# Hub's supporting components (RabbitMQ, VerneMQ, MinIO, ...) claim
# persistent volumes, so the cluster needs a working CSI driver.
###############################################################################
module "ebs_csi_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.44"
role_name = "${local.name}-ebs-csi"
attach_ebs_csi_policy = true
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
}
}
tags = local.tags
}
@@ -0,0 +1,5 @@
crds:
enabled: true
extraArgs:
- --acme-http01-solver-nameservers=1.1.1.1:53,8.8.8.8:53
@@ -0,0 +1,13 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
privateKeySecretRef:
name: letsencrypt-prod-account-key
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- http01:
ingress:
ingressClassName: nginx
@@ -0,0 +1,52 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hub-frontend-ingress
namespace: kerberos-hub
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
rules:
- host: aws-app.kerberos.lol
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hub-frontend-svc
port:
number: 80
tls:
- hosts:
- aws-app.kerberos.lol
secretName: aws-app-kerberos-lol-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hub-api-ingress
namespace: kerberos-hub
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: 200m
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
rules:
- host: aws-api.kerberos.lol
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hub-api-svc
port:
number: 8081
tls:
- hosts:
- aws-api.kerberos.lol
secretName: aws-api-kerberos-lol-tls
@@ -0,0 +1,7 @@
kerberoshub:
api:
schema: https
url: aws-api.kerberos.lol
frontend:
schema: https
url: aws-app.kerberos.lol
@@ -0,0 +1,15 @@
controller:
config:
use-forwarded-headers: "true"
ingressClass: nginx
ingressClassResource:
default: false
enabled: true
name: nginx
service:
annotations:
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-type: nlb
externalTrafficPolicy: Local
type: LoadBalancer
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly INGRESS_NGINX_VERSION="4.15.1"
readonly CERT_MANAGER_VERSION="v1.21.1"
for command in helm kubectl; do
if ! command -v "${command}" >/dev/null 2>&1; then
echo "Missing required command: ${command}" >&2
exit 1
fi
done
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx --force-update
helm repo add jetstack https://charts.jetstack.io --force-update
helm repo update ingress-nginx jetstack
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--version "${INGRESS_NGINX_VERSION}" \
--namespace ingress-nginx \
--create-namespace \
--values "${SCRIPT_DIR}/ingress-nginx-values.yaml" \
--atomic \
--wait \
--timeout 15m
helm upgrade --install cert-manager jetstack/cert-manager \
--version "${CERT_MANAGER_VERSION}" \
--namespace cert-manager \
--create-namespace \
--values "${SCRIPT_DIR}/cert-manager-values.yaml" \
--atomic \
--wait \
--timeout 15m
kubectl apply --filename "${SCRIPT_DIR}/cluster-issuer.yaml"
kubectl wait --for=condition=Ready clusterissuer/letsencrypt-prod --timeout=2m
kubectl apply --filename "${SCRIPT_DIR}/hub-ingresses.yaml"
kubectl get service ingress-nginx-controller \
--namespace ingress-nginx \
--output jsonpath='Load balancer: {.status.loadBalancer.ingress[0].hostname}{"\n"}'
+124
View File
@@ -0,0 +1,124 @@
###############################################################################
# Cluster
###############################################################################
output "region" {
description = "AWS region the stack is deployed in."
value = var.region
}
output "cluster_name" {
description = "Name of the EKS cluster."
value = module.eks.cluster_name
}
output "cluster_endpoint" {
description = "Endpoint of the Kubernetes API server."
value = module.eks.cluster_endpoint
}
output "update_kubeconfig_command" {
description = "Command to point kubectl at the new cluster."
value = "aws eks update-kubeconfig --region ${var.region} --name ${module.eks.cluster_name}"
}
output "vpc_id" {
description = "ID of the VPC. DocumentDB is only reachable from inside this VPC."
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
description = "IDs of the private subnets hosting the worker nodes and DocumentDB."
value = module.vpc.private_subnets
}
###############################################################################
# DocumentDB
###############################################################################
output "docdb_endpoint" {
description = "Cluster (writer) endpoint of the DocumentDB cluster."
value = aws_docdb_cluster.this.endpoint
}
output "docdb_reader_endpoint" {
description = "Reader endpoint of the DocumentDB cluster."
value = aws_docdb_cluster.this.reader_endpoint
}
output "docdb_port" {
description = "Port the DocumentDB cluster listens on."
value = aws_docdb_cluster.this.port
}
output "docdb_username" {
description = "DocumentDB master username."
value = aws_docdb_cluster.this.master_username
}
output "docdb_password" {
description = "DocumentDB master password. Read it with: terraform output -raw docdb_password"
value = local.docdb_password
sensitive = true
}
output "docdb_security_group_id" {
description = "Security group guarding the DocumentDB cluster."
value = aws_security_group.docdb.id
}
output "docdb_tls_enabled" {
description = "Whether TLS is enforced on the DocumentDB cluster."
value = var.docdb_tls
}
###############################################################################
# Hub wiring
###############################################################################
output "mongodb_uri" {
description = <<-EOT
Connection string for the Hub helm chart (`mongodb.uri`).
The chart appends `tls=true` and `tlsCAFile=...` itself when
`mongodb.tls.enabled=true`, so no TLS parameters are included here.
Read it with: terraform output -raw mongodb_uri
EOT
value = format(
"mongodb://%s:%s@%s:%d/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false",
var.docdb_username,
urlencode(local.docdb_password),
aws_docdb_cluster.this.endpoint,
aws_docdb_cluster.this.port,
)
sensitive = true
}
output "hub_values_snippet" {
description = <<-EOT
Ready to paste values for the Hub helm chart. Write it to a file with:
terraform output -raw hub_values_snippet > hub-documentdb-values.yaml
It expects the Amazon RDS CA bundle to be available as the `mongodb-ca` secret:
curl -O https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
kubectl create secret generic mongodb-ca --from-file=global-bundle.pem -n kerberos-hub
EOT
value = <<-EOT
mongodb:
# DocumentDB does not support geospatial queries, complex $lookup
# pipelines or retryable writes, hence the flavor and retryWrites below.
flavor: "documentdb"
retryWrites: "false"
uri: "mongodb://${var.docdb_username}:${urlencode(local.docdb_password)}@${aws_docdb_cluster.this.endpoint}:${aws_docdb_cluster.this.port}/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false"
adminDatabase: "admin"
authenticationMechanism: "SCRAM-SHA-1"
tls:
enabled: ${var.docdb_tls}
existingSecret: "mongodb-ca"
caFileName: "global-bundle.pem"
mountPath: "/certs"
EOT
sensitive = true
}
@@ -0,0 +1,33 @@
# Copy to terraform.tfvars and adjust.
name = "kerberos-hub"
region = "eu-west-1"
environment = "test"
# Networking
vpc_cidr = "10.20.0.0/16"
availability_zone_count = 3
single_nat_gateway = true
# EKS
kubernetes_version = "1.31"
# Restrict this to your office or VPN range.
cluster_endpoint_public_access_cidrs = ["0.0.0.0/0"]
node_instance_types = ["t3.large"]
node_desired_size = 2
node_min_size = 2
node_max_size = 4
# DocumentDB
docdb_engine_version = "5.0.0"
docdb_parameter_group_family = "docdb5.0"
docdb_instance_class = "db.t3.medium"
docdb_instance_count = 1
docdb_username = "kerberos"
# Leave docdb_password unset to have one generated:
# terraform output -raw docdb_password
docdb_tls = true
# Throwaway test stack settings, flip these for anything long lived.
docdb_deletion_protection = false
docdb_skip_final_snapshot = true
+194
View File
@@ -0,0 +1,194 @@
###############################################################################
# General
###############################################################################
variable "name" {
description = "Name prefix used for every resource created by this stack."
type = string
default = "kerberos-hub"
validation {
condition = can(regex("^[a-z][a-z0-9-]{2,30}$", var.name))
error_message = "The name must be lowercase, start with a letter and contain only letters, digits and dashes (3-31 characters)."
}
}
variable "region" {
description = "AWS region to deploy into."
type = string
default = "eu-west-1"
}
variable "environment" {
description = "Environment label applied as a tag (for example test, staging, production)."
type = string
default = "test"
}
variable "tags" {
description = "Extra tags merged into every resource."
type = map(string)
default = {}
}
###############################################################################
# Networking
###############################################################################
variable "vpc_cidr" {
description = "CIDR block of the VPC. DocumentDB is only reachable from within this VPC."
type = string
default = "10.20.0.0/16"
}
variable "availability_zone_count" {
description = "Number of availability zones to spread the subnets over. DocumentDB requires at least two."
type = number
default = 3
validation {
condition = var.availability_zone_count >= 2 && var.availability_zone_count <= 4
error_message = "availability_zone_count must be between 2 and 4."
}
}
variable "single_nat_gateway" {
description = "Use one shared NAT gateway instead of one per availability zone. Cheaper, but not highly available."
type = bool
default = true
}
###############################################################################
# EKS
###############################################################################
variable "kubernetes_version" {
description = "Kubernetes version of the EKS control plane."
type = string
default = "1.31"
}
variable "cluster_endpoint_public_access" {
description = "Expose the Kubernetes API server publicly. Keep it on for a test cluster, restrict it with cluster_endpoint_public_access_cidrs."
type = bool
default = true
}
variable "cluster_endpoint_public_access_cidrs" {
description = "CIDR blocks allowed to reach the public Kubernetes API endpoint. Narrow this to your office/VPN range."
type = list(string)
default = ["0.0.0.0/0"]
}
variable "node_instance_types" {
description = "Instance types of the managed node group."
type = list(string)
default = ["t3.large"]
}
variable "node_desired_size" {
description = "Desired number of worker nodes."
type = number
default = 2
}
variable "node_min_size" {
description = "Minimum number of worker nodes."
type = number
default = 2
}
variable "node_max_size" {
description = "Maximum number of worker nodes."
type = number
default = 4
}
variable "node_disk_size" {
description = "EBS volume size (GiB) of each worker node."
type = number
default = 50
}
###############################################################################
# DocumentDB
###############################################################################
variable "docdb_engine_version" {
description = "DocumentDB engine version."
type = string
default = "5.0.0"
}
variable "docdb_parameter_group_family" {
description = "Parameter group family matching the engine version (docdb5.0, docdb4.0, ...)."
type = string
default = "docdb5.0"
}
variable "docdb_instance_class" {
description = "Instance class of the DocumentDB instances."
type = string
default = "db.t3.medium"
}
variable "docdb_instance_count" {
description = "Number of DocumentDB instances. One is enough for a test stack, use two or more for failover."
type = number
default = 1
}
variable "docdb_username" {
description = "DocumentDB master username. 'admin' and other reserved words are rejected by AWS."
type = string
default = "kerberos"
}
variable "docdb_password" {
description = "DocumentDB master password. Leave null to generate one; read it afterwards with 'terraform output -raw docdb_password'."
type = string
default = null
sensitive = true
}
variable "docdb_tls" {
description = "Enforce TLS (encryption in transit) on the cluster. Keep this enabled; it is the configuration the hub chart's mongodb.tls values are meant for."
type = bool
default = true
}
variable "docdb_kms_key_id" {
description = "KMS key ARN for encryption at rest. Leave null to use the AWS managed key."
type = string
default = null
}
variable "docdb_backup_retention_period" {
description = "Number of days automated backups are retained."
type = number
default = 1
}
variable "docdb_deletion_protection" {
description = "Prevent the cluster from being deleted. Keep false for a throwaway test stack."
type = bool
default = false
}
variable "docdb_skip_final_snapshot" {
description = "Skip the final snapshot on destroy. Keep true for a throwaway test stack."
type = bool
default = true
}
variable "docdb_enabled_cloudwatch_logs_exports" {
description = "Log types exported to CloudWatch (audit, profiler)."
type = list(string)
default = []
}
variable "docdb_allowed_cidrs" {
description = "Extra CIDR blocks allowed to reach DocumentDB on port 27017, on top of the EKS worker nodes (for example a bastion subnet)."
type = list(string)
default = []
}
+18
View File
@@ -0,0 +1,18 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.60"
}
random = {
source = "hashicorp/random"
version = ">= 3.6"
}
}
}
provider "aws" {
region = var.region
}
+59
View File
@@ -0,0 +1,59 @@
locals {
name = var.name
tags = merge(
{
Project = "kerberos-hub"
Environment = var.environment
ManagedBy = "terraform"
Module = "deployment/modules/amazon-eks-documentdb"
},
var.tags,
)
azs = slice(data.aws_availability_zones.available.names, 0, var.availability_zone_count)
}
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
###############################################################################
# VPC
#
# DocumentDB has no public endpoint: it only listens inside the VPC. Both the
# EKS worker nodes and the DocumentDB instances therefore live in the private
# subnets, and the workers reach the internet (image pulls) through NAT.
###############################################################################
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.13"
name = "${local.name}-vpc"
cidr = var.vpc_cidr
azs = local.azs
private_subnets = [for index in range(var.availability_zone_count) : cidrsubnet(var.vpc_cidr, 4, index)]
public_subnets = [for index in range(var.availability_zone_count) : cidrsubnet(var.vpc_cidr, 4, index + 8)]
enable_nat_gateway = true
single_nat_gateway = var.single_nat_gateway
enable_dns_hostnames = true
enable_dns_support = true
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = "1"
}
tags = local.tags
}
+1 -1
View File
@@ -36,7 +36,7 @@ helmCharts:
releaseName: hub
namespace: kerberos-hub
repo: https://charts.kerberos.io
version: 0.84.0
version: 0.170.1
valuesFile: ../../base/hub/kerberos-hub-values.yaml
valuesInline:
license: "L/+FAwEBB2xpY2Vuc2UB/4YAAQIBB1BheWxvYWQBCgABCVNpZ25hdHVyZQEKAAAA/gMw/4YB/gEl/8wQACxnaXRodWIuY29tL3V1Zy1haS9odWItbGljZW5zZS9tb2RlbHMuTGljZW5zZf+BAwEBB0xpY2Vuc2UB/4IAAQwBAklkAf+EAAEDS2V5AQwAAQdDb21wYW55AQwAAQdFeHBUaW1lAQQAAQREYXlzAQQAAQdDYW1lcmFzAQQAAQVTaXRlcwEEAAEGVmF1bHRzAQQAAQpNZWRpYUxpbWl0AQQAAQlQZXJwZXR1YWwBAgABBkFjdGl2ZQECAAEJSXBBZGRyZXNzAQwAAAAY/4MBAQEIT2JqZWN0SUQB/4QAAQYBGAAAPf+COgEMAAAAAAAAAAAAAAAAAg9nZW5lcmljLWxpY2Vuc2UB+AJl6LAHsRMYAfoB0alKIAABEAEUARQDAQAB/gIAFlQqo9yJj1a3OH1+5iGjCPcz9VtrRuGfz/iuQEFohbCfSiZGAdHh16gEc59Z2kPaey+eJcwY0BQsBoipW7SwFRmcHkrWLuxt+Yzi2kvwmL4TN0quWqM4xxBzH1sejS6kZpPm7bw11JBsBuaNOYMbl5lgpPCFajWUH6GzfmuQlptNQKosocacI/9PaaqEUUM0ALjDZNSqqFwZPPsFh9WTeaHGbRbmwQRMPFDgaPCxiHnCCUagbvuVZeOQbKaR5gPTUfPQY/L4mrN9QnMxdzwUJgqm7FbTH4UhoZn6EEzNGTN6DbHhpx8lezzCRW8dzMNuK8R1igiORV5gdWOPzBMZlTgodIZRD07e8iAo2XqT/BWcqaJXutdqXltaiJKshh2QzStdv2WZUynVTguYIQJyBNhrXEkewKZA0UftcSemDPkCmp6VovMMveV4X3Cpzkm7ZHtE7j39At/kpuqY/icTEzRWYuQqEDtqXMVar84VOpgVhxeNrrqZniLq8o3ezRdwy0BJG5xT9cuMxq13ZB3Am9iqTSjx2mh5VZZeB33GjLVAtLscj8NoKWy7BThchXB9xwoiy2RLO8nE+EI8wPbg+ajb2Ta7AFyxHgJJU6cea+zEpiBMARwjPKqk2nn02xdfdqMzQ+E9e115sIqhotBM5RI7uBIf8DDwT93HLsJZRBAA"
+1 -1
View File
@@ -26,7 +26,7 @@ helmCharts:
releaseName: hub
namespace: kerberos-hub
repo: https://charts.kerberos.io
version: 0.84.0
version: 0.170.1
valuesFile: ../../base/hub/kerberos-hub-values.yaml
valuesInline:
mqtt:
+1 -1
View File
@@ -8,7 +8,7 @@ helmCharts:
releaseName: hub
namespace: kerberos-hub
repo: https://charts.kerberos.io
version: 0.84.0
version: 0.170.1
valuesFile: ../../base/hub/kerberos-hub-values.yaml
valuesInline:
kerberoshub:
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "${CI:-false}" != true ]]; then
echo "This smoke test changes Factory settings and restarts its pod. Run it only in a disposable CI cluster (CI=true)." >&2
exit 1
fi
temporary="$(mktemp -d)"
pid=""
stop_forward() {
if [[ -n "$pid" ]]; then
if kill -0 "$pid" 2>/dev/null; then
kill "$pid"
wait "$pid" || [[ "$?" == 143 ]]
fi
pid=""
fi
}
cleanup() {
stop_forward
rm -f "$temporary/port-forward.log" "$temporary/login.json" "$temporary/config.json" "$temporary/saved.json" "$temporary/index.html"
rmdir "$temporary"
}
trap cleanup EXIT
kubectl -n kerberos-factory rollout status deployment/factory --timeout=300s
kubectl -n kerberos-factory get deployment factory -o json | jq -e '
.spec.template.spec as $pod |
$pod.securityContext.fsGroup == 65532 and
$pod.securityContext.seccompProfile.type == "RuntimeDefault" and
($pod.containers[] | select(.name == "factory") |
.securityContext.runAsNonRoot == true and
.securityContext.runAsUser == 65532 and .securityContext.runAsGroup == 65532 and
.securityContext.readOnlyRootFilesystem == true and
.securityContext.allowPrivilegeEscalation == false and
.securityContext.capabilities.drop == ["ALL"] and
.ports == [{"name":"http","containerPort":8080,"protocol":"TCP"}] and
any(.volumeMounts[]; .mountPath == "/home/factory/config") and
any(.volumeMounts[]; .mountPath == "/tmp") and
.startupProbe.httpGet.path == "/healthz" and
.readinessProbe.httpGet.port == "http" and
.livenessProbe.httpGet.port == "http")'
kubectl -n kerberos-factory get service factory-nodeport -o json |
jq -e '.spec.ports | any(.port == 80 and .targetPort == "http" and .nodePort == 30079)'
kubectl -n kerberos-factory get configmap agent-global-config agent-template-config
kubectl -n kerberos-factory exec deployment/factory -- /app/main healthcheck
forward() {
kubectl -n kerberos-factory port-forward service/factory-nodeport 13979:80 > "$temporary/port-forward.log" 2>&1 &
pid=$!
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:13979/healthz >/dev/null; then return; fi
if ! kill -0 "$pid" 2>/dev/null; then break; fi
sleep 1
done
cat "$temporary/port-forward.log" >&2
echo "Factory Service port-forward did not become healthy." >&2
exit 1
}
login() {
curl --fail --silent --show-error -H 'Content-Type: application/json' \
-d '{"username":"root","password":"kerberos"}' \
http://127.0.0.1:13979/api/login > "$temporary/login.json"
token="$(jq -er '.token | select(length > 0)' "$temporary/login.json")"
if [[ "${GITHUB_ACTIONS:-false}" == true ]]; then echo "::add-mask::$token"; fi
}
forward
curl --fail --silent --show-error http://127.0.0.1:13979/ > "$temporary/index.html"
grep -qi '<html' "$temporary/index.html"
test "$(curl --silent --output /dev/null --write-out '%{http_code}' http://127.0.0.1:13979/api/config)" = 401
login
curl --fail --silent --show-error -H "Authorization: Bearer $token" \
http://127.0.0.1:13979/api/config > "$temporary/config.json"
jq -e '.data.type == "global"' "$temporary/config.json"
jq '.data | .timezone = (if .timezone == "UTC" then "Etc/GMT+1" else "UTC" end)' \
"$temporary/config.json" > "$temporary/saved.json"
timezone="$(jq -r '.timezone' "$temporary/saved.json")"
curl --fail --silent --show-error -H "Authorization: Bearer $token" \
-H 'Content-Type: application/json' --data-binary "@$temporary/saved.json" \
http://127.0.0.1:13979/api/config | jq -e --arg timezone "$timezone" '.data.timezone == $timezone'
kubectl -n kerberos-factory get configmap agent-global-config -o json |
jq -e --arg timezone "$timezone" '.data["_source.json"] | fromjson | .timezone == $timezone'
pod_uid() {
kubectl -n kerberos-factory get pods -l app=factory -o json |
jq -er '[.items[] | select(.metadata.deletionTimestamp == null)][0].metadata.uid'
}
old_pod="$(pod_uid)"
stop_forward
kubectl -n kerberos-factory rollout restart deployment/factory
kubectl -n kerberos-factory rollout status deployment/factory --timeout=300s
test "$old_pod" != "$(pod_uid)"
forward
login
curl --fail --silent --show-error -H "Authorization: Bearer $token" \
http://127.0.0.1:13979/api/config | jq -e --arg timezone "$timezone" '.data.timezone == $timezone'
kubectl -n kerberos-factory exec deployment/factory -- /app/main healthcheck
echo "Factory passed: restricted runtime, Service routing, UI/auth/config, ConfigMap bootstrap and persisted settings after pod replacement."
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -gt 1 || ( $# -eq 1 && "$1" != --restart ) ]]; then
echo "Usage: bash scripts/check-garage.sh [--restart]" >&2
exit 1
fi
if ! command -v aws > /dev/null; then
echo "AWS CLI is required for the Garage S3 smoke test." >&2
exit 1
fi
temporary="$(mktemp -d)"
pid=""
cleanup() {
if [[ -n "$pid" ]]; then kill "$pid"; fi
rm -f "$temporary/upload" "$temporary/download" "$temporary/port-forward.log" "$temporary/aws-config"
rmdir "$temporary"
}
trap cleanup EXIT
access_key="$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data.access-key}' | base64 --decode)"
secret_key="$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data.secret-key}' | base64 --decode)"
if [[ "${GITHUB_ACTIONS:-false}" == true ]]; then
echo "::add-mask::$access_key"
echo "::add-mask::$secret_key"
fi
forward() {
kubectl -n garage port-forward service/garage 13900:3900 > "$temporary/port-forward.log" 2>&1 &
pid=$!
for attempt in {1..30}; do
if curl --silent --output /dev/null http://127.0.0.1:13900/; then return; fi
if ! kill -0 "$pid"; then break; fi
sleep 1
done
cat "$temporary/port-forward.log" >&2
echo "Garage port-forward did not become ready." >&2
exit 1
}
forward
export AWS_ACCESS_KEY_ID="$access_key" AWS_SECRET_ACCESS_KEY="$secret_key"
export AWS_DEFAULT_REGION=garage AWS_EC2_METADATA_DISABLED=true
export AWS_PAGER=""
unset AWS_SESSION_TOKEN AWS_PROFILE AWS_DEFAULT_PROFILE
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
export AWS_CONFIG_FILE="$temporary/aws-config"
printf '[default]\ns3 =\n addressing_style = path\n' > "$AWS_CONFIG_FILE"
object="ci-smoke/$(openssl rand -hex 16)"
openssl rand -hex 128 > "$temporary/upload"
endpoint="http://127.0.0.1:13900"
aws --endpoint-url "$endpoint" s3api put-object \
--bucket mybucket --key "$object" --body "$temporary/upload"
if [[ $# -eq 1 ]]; then
kill "$pid"
wait "$pid" || [[ "$?" == 143 ]]
pid=""
kubectl -n garage rollout restart statefulset/garage
kubectl -n garage rollout status statefulset/garage --timeout=300s
forward
fi
aws --endpoint-url "$endpoint" s3api get-object \
--bucket mybucket --key "$object" "$temporary/download"
cmp "$temporary/upload" "$temporary/download"
echo "Garage S3 authenticated PUT/GET passed through the Kubernetes Service (object mybucket/$object retained)."
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CHART="${1:-kerberos/hub}"
version=()
if [[ "$CHART" == kerberos/hub ]]; then version=(--version 0.170.1); fi
assert_contains() {
if ! grep -Eq -- "$2" <<< "$1"; then
printf 'Missing expected deployment runtime setting: %s\n' "$2" >&2
exit 1
fi
}
nodeport="$(awk '
/^---/ { selected=0 }
/name: hub-frontend-nodeport$/ { selected=1 }
selected { print }
' "$ROOT/base/hub/kerberos-hub-nodeports.yaml")"
assert_contains "$nodeport" 'targetPort: http$'
assert_contains "$nodeport" 'nodePort: 32080'
rendered="$(helm template hub "$CHART" "${version[@]}" --values "$ROOT/base/hub/kerberos-hub-values.yaml" \
--set mode=ui --set kerberoshub.frontend.demoEnabled=true)"
if grep -q 'image: "docker.io/ghcr.io/' <<< "$rendered"; then
echo "Fully qualified GHCR image was prefixed with docker.io" >&2
exit 1
fi
for name in hub-frontend hub-frontend-demo; do
deployment="$(awk -v name="$name" '
function emit() { if (deployment && matched) printf "%s", block }
/^---$/ { emit(); block=""; deployment=0; matched=0 }
{ block=block $0 "\n" }
/^kind: Deployment$/ { deployment=1 }
$0 == " name: " name { matched=1 }
END { emit() }
' <<< "$rendered")"
for setting in 'containerPort: 8080' 'runAsNonRoot: true' 'runAsUser: 65532' \
'readOnlyRootFilesystem: true' 'mountPath: /tmp' 'name: frontend-tmp'; do
assert_contains "$deployment" "^[[:space:]]*(- )?$setting$"
done
assert_contains "$deployment" 'image: "ghcr.io/uug-ai/hub-frontend:v1.19.28"$'
echo "OK: $name uses the promoted image and matching hardened runtime"
done
api="$(awk '
function emit() { if (deployment && matched) printf "%s", block }
/^---$/ { emit(); block=""; deployment=0; matched=0 }
{ block=block $0 "\n" }
/^kind: Deployment$/ { deployment=1 }
/^ name: hub-api$/ { matched=1 }
END { emit() }
' <<< "$rendered")"
for setting in 'runAsNonRoot: true' 'runAsUser: 65532' 'runAsGroup: 65532' \
'fsGroup: 65532' 'readOnlyRootFilesystem: true' 'allowPrivilegeEscalation: false' \
'type: RuntimeDefault' 'drop:' '- ALL' 'name: hub-api-tmp' \
'emptyDir: \{\}' 'mountPath: /tmp'; do
assert_contains "$api" "^[[:space:]]*(- )?$setting$"
done
assert_contains "$api" 'image: "ghcr.io/uug-ai/hub-api:v1.11.31"$'
echo "OK: hub-api uses the promoted image, hardened security and writable scratch"
echo "Deployment Hub runtime preflight passed."
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -uo pipefail
kubectl get pods,pvc -A -o wide
kubectl get events -A --sort-by=.lastTimestamp
for namespace in garage mongodb rabbitmq vernemq kerberos-vault kerberos-agent kerberos-hub kerberos-factory; do
if ! pods="$(kubectl -n "$namespace" get pods -o name)"; then
echo "::warning::Could not list pods in $namespace."
continue
fi
for pod in $pods; do
if ! kubectl -n "$namespace" describe "$pod"; then
echo "::warning::Could not describe $namespace/$pod."
fi
if ! kubectl -n "$namespace" logs "$pod" --all-containers=true --tail=80 --prefix; then
echo "::warning::Logs unavailable for $namespace/$pod."
fi
done
done
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [[ $# -gt 1 || ( $# -eq 1 && "$1" != --prepare-only ) ]]; then
echo "Usage: bash scripts/install-garage.sh [--prepare-only]" >&2
exit 1
fi
kubectl apply -f "$ROOT/base/garage/namespace.yaml"
secret="$(kubectl -n garage get secret garage-credentials --ignore-not-found -o name)"
if [[ -z "$secret" ]]; then
pvc="$(kubectl -n garage get pvc data-garage-0 --ignore-not-found -o name)"
if [[ -n "$pvc" ]]; then
echo "Garage data already exists without its credentials. Restore garage-credentials from backup; refusing to generate replacement keys." >&2
exit 1
fi
kubectl -n garage create secret generic garage-credentials \
--from-literal="rpc-secret=$(openssl rand -hex 32)" \
--from-literal="access-key=GK$(openssl rand -hex 16)" \
--from-literal="secret-key=$(openssl rand -hex 32)"
fi
if ! kubectl -n garage get secret garage-credentials -o json |
jq -e '.data |
(."rpc-secret" | @base64d | test("^[0-9a-f]{64}$")) and
(."access-key" | @base64d | test("^GK[0-9a-f]{32}$")) and
(."secret-key" | @base64d | test("^[0-9a-f]{64}$"))' > /dev/null; then
echo "Garage credentials are missing or invalid; restore a valid garage-credentials Secret." >&2
exit 1
fi
if [[ $# -eq 1 ]]; then
echo "Garage credentials are ready; apply your Kustomize overlay next."
exit 0
fi
kubectl apply -k "$ROOT/base/garage"
kubectl -n garage rollout status statefulset/garage --timeout=300s
kubectl -n garage exec garage-0 -- /garage bucket info mybucket > /dev/null
echo "Garage is available at garage.garage:3900 (region garage, bucket mybucket). Credentials are in Secret garage/garage-credentials."
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# != 1 ]]; then
echo "Usage: bash scripts/notify-slack.sh MESSAGE" >&2
exit 1
fi
if [[ -z "${SLACK_WEBHOOK_URL:-}" ]]; then
echo "::warning::SLACK_WEBHOOK_URL is not configured; skipping optional Slack notification."
exit 0
fi
jq -n --arg text "$1" '{text: $text}' |
curl --fail-with-body --silent --show-error --retry 3 \
-H 'Content-Type: application/json' --data-binary @- "$SLACK_WEBHOOK_URL"
+45
View File
@@ -0,0 +1,45 @@
import assert from 'node:assert/strict'
import { readFile, writeFile } from 'node:fs/promises'
const root = new URL('../', import.meta.url)
const names = ['how-it-works', 'deployment-self-hosted', 'deployment-managed']
const check = process.argv.includes('--check')
assert(process.argv.slice(2).every(arg => arg === '--check'), 'Usage: node scripts/update-diagram-links.mjs [--check]')
const links = []
for (const name of names) {
const diagram = JSON.parse(await readFile(new URL(`assets/diagrams/${name}.json`, root), 'utf8'))
assert(Array.isArray(diagram.groups) && Array.isArray(diagram.nodes) && Array.isArray(diagram.connections), `${name}: missing diagram arrays`)
const ids = new Set()
for (const node of diagram.nodes) {
assert(typeof node.id === 'string' && node.id && !ids.has(node.id), `${name}: invalid or duplicate node id`)
ids.add(node.id)
}
for (const item of [...diagram.groups, ...diagram.nodes]) {
assert([item.x, item.y, item.w, item.h].every(Number.isFinite) && item.w > 0 && item.h > 0, `${name}: invalid bounds`)
}
for (const connection of diagram.connections) {
assert(ids.has(connection.from) && ids.has(connection.to), `${name}: dangling connection`)
for (const side of [connection.fromSide, connection.toSide]) {
assert(['left', 'right', 'top', 'bottom'].includes(side), `${name}: invalid connection side`)
}
}
const preview = await readFile(new URL(`assets/images/${name}.svg`, root), 'utf8')
assert(preview.startsWith('<svg') && !/<(?:script|foreignObject)\b/i.test(preview), `${name}: expected a GitHub-safe SVG export`)
const payload = Buffer.from(JSON.stringify(diagram), 'utf8').toString('base64')
links.push(`[draw-${name}]: https://draw.uug.ai/#diagram=${payload}`)
}
const file = new URL('README.md', root)
const readme = await readFile(file, 'utf8')
const start = '<!-- BEGIN GENERATED DRAW LINKS -->'
const end = '<!-- END GENERATED DRAW LINKS -->'
assert(readme.split(start).length === 2 && readme.split(end).length === 2, 'Expected one Draw links section in README.md')
const section = `${start}\n${links.join('\n')}\n${end}`
const updated = readme.replace(/<!-- BEGIN GENERATED DRAW LINKS -->[\s\S]*?<!-- END GENERATED DRAW LINKS -->/, section)
if (check) {
assert.equal(updated, readme, 'Draw links are stale. Run node scripts/update-diagram-links.mjs')
} else if (updated !== readme) {
await writeFile(file, updated)
}
console.log(`${check ? 'Verified' : 'Updated'} ${names.length} editable Draw links and checked their sources/previews.`)