2 Commits

Author SHA1 Message Date
Danijel Simeunovic
ac0f464b2a fixes 2026-03-19 15:42:41 +01:00
Danijel Simeunovic
a681a9ae81 multi cluster 2026-03-18 22:28:38 +01:00
97 changed files with 1497 additions and 7943 deletions

View File

@@ -1,32 +0,0 @@
name: Deploy Gitea Pages
on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'Dockerfile.docs'
- 'nginx.conf'
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: pip install mkdocs mkdocs-material
- run: mkdocs build
- name: Deploy to Gitea Pages
run: |
cd site
git init
git config user.name "gitea-actions"
git config user.email "actions@forteapps.net"
git add .
git commit -m "Deploy docs"
git push --force "https://x-token:${{ gitea.token }}@git.forteapps.net/Forte/launchpad.git" HEAD:gitea-pages

2
.gitignore vendored
View File

@@ -6,6 +6,7 @@
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
.github/
private/ private/
.helm/ .helm/
temp/ temp/
@@ -15,4 +16,3 @@ CLAUDE.md
devbox.d/ devbox.d/
devbox.lock devbox.lock
.devbox/ .devbox/
bash.exe.stackdump

View File

@@ -1,7 +0,0 @@
standards_version: "2025.1"
last_configured: "2026-04-18"
components:
gitea-pages: "2025.1"
gitea-pages-generator: "mkdocs"
gitea-pages-source: "docs/"
gitea-pages-theme: "material"

View File

@@ -4,7 +4,6 @@
[![GitOps](https://img.shields.io/badge/GitOps-ArgoCD-blue)](https://argoproj.github.io/cd/) [![GitOps](https://img.shields.io/badge/GitOps-ArgoCD-blue)](https://argoproj.github.io/cd/)
[![Kubernetes](https://img.shields.io/badge/Kubernetes-UpCloud-orange)](https://upcloud.com/) [![Kubernetes](https://img.shields.io/badge/Kubernetes-UpCloud-orange)](https://upcloud.com/)
[![Docs](https://img.shields.io/badge/Docs-Gitea%20Pages-green)](https://git.forteapps.net/Forte/launchpad/pages/)
--- ---
@@ -12,8 +11,6 @@
**New developers and operators**: Please refer to our comprehensive documentation for detailed guides and references: **New developers and operators**: Please refer to our comprehensive documentation for detailed guides and references:
### 🌐 [**Live Documentation Site**](https://git.forteapps.net/Forte/launchpad/pages/) (Gitea Pages)
### 🎯 [**START HERE: Documentation Index**](docs/README.md) ### 🎯 [**START HERE: Documentation Index**](docs/README.md)
| Document | Description | Audience | | Document | Description | Audience |
@@ -30,8 +27,8 @@
### For New Developers ### For New Developers
```bash ```bash
# 1. Clone repositories # 1. Clone repositories
git clone https://git.forteapps.net/Forte/launchpad.git git clone https://github.com/fortedigital/sturdy-adventure.git
git clone ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git git clone git@github.com:fortedigital/helm-values.git
# 2. Read the guides # 2. Read the guides
# - Start: docs/GITOPS-ARCHITECTURE.md # - Start: docs/GITOPS-ARCHITECTURE.md
@@ -60,10 +57,10 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
### What's Inside ### What's Inside
- **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Sealed Secrets - **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Sealed Secrets
- **Business Applications**: MCP10X, MusicMan, Dot-AI Stack, ArgoCD MCP - **Business Applications**: MCP10X, MusicMan, Dot-AI Stack, ArgoCD MCP
- **Policies**: Kyverno security policies for secret management, namespace controls, pod verification - **Policies**: Kyverno security policies for secret management, namespace controls, pod verification
- **Monitoring**: Full observability stack with metrics, logs, traces, and alerting - **Monitoring**: Full observability stack with metrics, logs, and alerting
- **Secrets**: Sealed Secrets for secure Git storage - **Secrets**: Sealed Secrets for secure Git storage
### Key Features ### Key Features
@@ -75,7 +72,7 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
**Policy Enforcement**: Kyverno ensures security and compliance **Policy Enforcement**: Kyverno ensures security and compliance
**Authentication**: Automatic sidecar injection (token & OIDC support) **Authentication**: Automatic sidecar injection (token & OIDC support)
**TLS Everywhere**: Automatic Let's Encrypt certificates **TLS Everywhere**: Automatic Let's Encrypt certificates
**Full Observability**: Prometheus, Grafana, Loki, Tempo integration **Full Observability**: Prometheus, Grafana, Loki integration
--- ---
@@ -85,31 +82,19 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
. .
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern) ├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern)
├── mkdocs.yml # MkDocs configuration (Gitea Pages)
├── .gitea/workflows/ # Gitea Actions CI workflows ├── infra/ # Infrastructure ArgoCD Applications
── docs.yaml # Build & deploy MkDocs to Gitea Pages ── enterprise-apps.yaml # Manages all apps in apps/ folder
├── traefik-application.yaml
├── infra/ # Infrastructure ArgoCD Applications (Kustomize multi-cluster) │ ├── cert-manager-application.yaml
│ ├── base/ # Base ArgoCD Application manifests (EU defaults) │ ├── kyverno.yaml
│ ├── kustomization.yaml │ ├── prometheus.yaml
│ ├── traefik-application.yaml │ ├── grafana.yaml
│ ├── keycloak.yaml │ ├── loki.yaml
│ ├── grafana.yaml │ ├── fluent-bit.yaml
│ ├── gitea.yaml │ ├── trivy.yaml
│ ├── gitea-actions.yaml │ ├── sealedsecrets.yaml
│ │ ├── tempo.yaml
│ │ ├── renovate.yaml
│ │ ├── ... # All other Application manifests
│ │ └── secrets.yaml
│ ├── overlays/ # Per-cluster overrides
│ │ ├── upc-dev/ # UpCloud Dev cluster (uses base as-is)
│ │ └── upc-prod/ # UpCloud Prod cluster (patches value paths)
│ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides │ └── values/ # Helm value overrides
│ ├── base/ # Shared values (all clusters)
│ ├── upc-dev/ # UpCloud Dev-specific values
│ └── upc-prod/ # UpCloud Prod-specific values
├── apps/ # Business Applications ├── apps/ # Business Applications
│ ├── mcp10x.yaml │ ├── mcp10x.yaml
@@ -151,9 +136,9 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
| Repository | Purpose | Who Edits | How Often | | Repository | Purpose | Who Edits | How Often |
|------------|---------|-----------|-----------| |------------|---------|-----------|-----------|
| **[launchpad](https://git.forteapps.net/Forte/launchpad)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often | | **[sturdy-adventure](https://github.com/fortedigital/sturdy-adventure.git)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often |
| **[forte-helm](https://git.forteapps.net/Forte/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely | | **[forte-helm](https://github.com/snothub/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely |
| **[helm-values](ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes | | **[helm-values](git@github.com:fortedigital/helm-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes |
### GitOps Workflow ### GitOps Workflow
@@ -346,12 +331,8 @@ kubectl patch application myapp -n argocd \
| **Prometheus** | Metrics | `monitoring` | 1 | | **Prometheus** | Metrics | `monitoring` | 1 |
| **Grafana** | Dashboards | `monitoring` | 1 | | **Grafana** | Dashboards | `monitoring` | 1 |
| **Loki** | Logs | `monitoring` | 1 | | **Loki** | Logs | `monitoring` | 1 |
| **Tempo** | Distributed tracing | `monitoring` | 1 |
| **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet | | **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet |
| **OpenCost** | Cost monitoring | `monitoring` | 1 |
| **Renovate** | Dependency updates | `renovate` | CronJob |
| **Trivy** | Vulnerability scanning | `trivy-system` | 1 | | **Trivy** | Vulnerability scanning | `trivy-system` | 1 |
| **Gitea Pages** | Documentation hosting | N/A (Gitea built-in) | N/A |
**Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components) **Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components)
@@ -369,7 +350,7 @@ kubectl patch application myapp -n argocd \
## 📖 Key Concepts ## 📖 Key Concepts
### App-of-Apps Pattern ### App-of-Apps Pattern
`_app-of-apps.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{upc-dev,upc-prod}/` render the base Applications with per-cluster patches (e.g., swapping value file paths from `upc-dev` to `upc-prod`). `_app-of-apps.yaml` is the root Application that manages all other Applications in `infra/`. Each YAML in `infra/` becomes a child Application managed by ArgoCD.
### Multi-Source Pattern ### Multi-Source Pattern
Applications reference both: Applications reference both:
@@ -468,14 +449,14 @@ Documentation lives in `docs/`. To update:
### Current Environment ### Current Environment
- **Provider**: UpCloud Managed Kubernetes - **Provider**: UpCloud Managed Kubernetes
- **Environment**: Production (internal use only) - **Environment**: Production (internal use only)
- **Clusters**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays - **Cluster**: Single cluster
- **Auth**: Disabled for ArgoCD (internal access) - **Auth**: Disabled for ArgoCD (internal access)
- **Backup**: None (cluster rebuildable via GitOps) - **Backup**: None (cluster rebuildable via GitOps)
### Known Limitations ### Known Limitations
- No automated backups (yet) - No automated backups (yet)
- Secret rotation not automated - Secret rotation not automated
- Multi-cluster limited to upc-dev and upc-prod environments - Single cluster (no multi-cluster setup)
- DNS management is manual - DNS management is manual
**Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery) **Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery)
@@ -489,11 +470,10 @@ Documentation lives in `docs/`. To update:
- [Kyverno Documentation](https://kyverno.io/docs/) - [Kyverno Documentation](https://kyverno.io/docs/)
- [Traefik Documentation](https://doc.traefik.io/traefik/) - [Traefik Documentation](https://doc.traefik.io/traefik/)
- [Cert-Manager Documentation](https://cert-manager.io/docs/) - [Cert-Manager Documentation](https://cert-manager.io/docs/)
- [Grafana Tempo Documentation](https://grafana.com/docs/tempo/)
- [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) - [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
### Related Repositories ### Related Repositories
- [forte-helm](https://github.com/fortedigital/forte-helm) - Helm chart templates - [forte-helm](https://github.com/snothub/forte-helm) - Helm chart templates
- [helm-values](git@github.com:fortedigital/helm-values.git) - Application values - [helm-values](git@github.com:fortedigital/helm-values.git) - Application values
--- ---
@@ -512,7 +492,7 @@ Internal use only. Not for public distribution.
--- ---
**Last Updated**: 2026-04-18 **Last Updated**: 2026-03-16
**Documentation Version**: 1.0.0 **Documentation Version**: 1.0.0
**🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!** **🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!**

View File

@@ -20,7 +20,7 @@ spec:
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
path: infra/overlays/upc-prod path: infra/overlays/eu
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: default namespace: default

View File

@@ -18,9 +18,9 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
path: infra/overlays/upc-dev path: infra/overlays/us
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: default namespace: default

View File

@@ -16,14 +16,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git - repoURL: https://github.com/snothub/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/argocd-mcp/values.yaml - $values/argocd-mcp/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git - repoURL: git@github.com:fortedigital/helm-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -35,7 +35,7 @@ spec:
releaseName: dot-ai-stack releaseName: dot-ai-stack
valueFiles: valueFiles:
- $values/infra/values/base/dot-ai-stack-values.yaml - $values/infra/values/base/dot-ai-stack-values.yaml
- $values/infra/values/upc-dev/dot-ai-stack-values.yaml - $values/infra/values/eu/dot-ai-stack-values.yaml
- repoURL: git@github.com:fortedigital/sturdy-adventure.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD

View File

@@ -17,14 +17,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git - repoURL: https://github.com/snothub/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/mcp10x/values.yaml - $values/mcp10x/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git - repoURL: git@github.com:fortedigital/helm-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -1,42 +1,39 @@
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
metadata: metadata:
name: opencost name: mcpcoder
namespace: argocd namespace: argocd
annotations: annotations:
argocd.argoproj.io/sync-wave: "1" argocd.argoproj.io/sync-wave: "1"
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels: labels:
app.kubernetes.io/name: opencost app.kubernetes.io/name: mcpcoder
app.kubernetes.io/part-of: monitoring app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd app.kubernetes.io/managed-by: argocd
finalizers: finalizers:
- resources-finalizer.argocd.argoproj.io - resources-finalizer.argocd.argoproj.io
spec: spec:
project: default project: default
sources: sources:
- repoURL: https://opencost.github.io/opencost-helm-chart - repoURL: https://github.com/snothub/forte-helm
chart: opencost path: forteapp
targetRevision: "1.42.0" targetRevision: HEAD
helm: helm:
releaseName: opencost
valueFiles: valueFiles:
- $values/infra/values/opencost-values.yaml - $values/mcpcoder/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: git@github.com:fortedigital/helm-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: monitoring namespace: mcpcoder
syncPolicy: syncPolicy:
automated: automated:
prune: true prune: true
selfHeal: true selfHeal: true
allowEmpty: false
syncOptions: syncOptions:
- CreateNamespace=true - CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

@@ -17,14 +17,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git - repoURL: https://github.com/snothub/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/musicman/values.yaml - $values/musicman/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git - repoURL: git@github.com:fortedigital/helm-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
# No patches needed — base already has "eu" paths
# EU is the default/base cluster

View File

@@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
# No patches needed — base already has "upc-dev" paths
# upc-dev is the default/base cluster

View File

@@ -4,11 +4,11 @@ resources:
- ../../base - ../../base
patches: patches:
# dot-ai-stack: swap upc-dev → upc-prod # dot-ai-stack: swap eu → us
- target: - target:
kind: Application kind: Application
name: dot-ai-stack name: dot-ai-stack
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/dot-ai-stack-values.yaml value: $values/infra/values/us/dot-ai-stack-values.yaml

View File

@@ -17,7 +17,7 @@ echo "Bootstrapping cluster: ${clusterName} (${CLUSTER})..."
Bootstrap() Bootstrap()
{ {
ArgoCd ArgoCd
# Github Github
} }

View File

@@ -1,88 +0,0 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: gitea-backup
namespace: gitea
spec:
schedule: "0 3 * * *" # daily at 03:00 UTC
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 1
activeDeadlineSeconds: 1800
template:
spec:
restartPolicy: Never
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
# Must run on the same node as Gitea to share the RWO volume
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: gitea
topologyKey: kubernetes.io/hostname
initContainers:
- name: gitea-dump
image: gitea/gitea:1.25.4
command:
- sh
- -c
- |
gitea dump \
-c /data/gitea/conf/app.ini \
-f /backup/gitea-dump.zip \
-t /tmp/gitea-dump && \
echo "Dump completed: $(ls -lh /backup/gitea-dump.zip)"
volumeMounts:
- name: data
mountPath: /data
readOnly: true
- name: backup
mountPath: /backup
- name: tmp
mountPath: /tmp/gitea-dump
containers:
- name: upload
image: minio/mc:latest
env:
- name: HOME
value: /tmp
command:
- sh
- -c
- |
mc alias set upcloud "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
KEY="gitea-dump-${TIMESTAMP}.zip"
echo "Uploading ${KEY}..."
mc cp /backup/gitea-dump.zip "upcloud/${S3_BUCKET}/${KEY}" && \
echo "Upload complete."
# Prune backups older than 7 days
echo "Pruning backups older than 7 days..."
mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true
echo "Pruning complete."
envFrom:
- secretRef:
name: gitea-backup-s3
volumeMounts:
- name: backup
mountPath: /backup
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: gitea-shared-storage
- name: backup
emptyDir:
sizeLimit: 5Gi
- name: tmp
emptyDir:
sizeLimit: 5Gi

View File

@@ -1,13 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitea-ssh
namespace: gitea
spec:
entryPoints:
- giteassh
routes:
- match: HostSNI(`*`)
services:
- name: gitea-ssh
port: 22

View File

@@ -1,37 +0,0 @@
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: deny-external-egress
namespace: trivy-system
labels:
app.kubernetes.io/managed-by: argocd
app.kubernetes.io/part-of: network-policies
spec:
endpointSelector: {}
egress:
# Allow DNS resolution
- toEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
# Allow cluster-internal traffic (RFC1918)
- toCIDR:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
# Allow Trivy vulnerability DB downloads (ghcr.io OCI registry)
- toFQDNs:
- matchName: ghcr.io
- matchName: pkg-containers.githubusercontent.com
toPorts:
- ports:
- port: "443"
protocol: TCP

View File

@@ -10,7 +10,7 @@ metadata:
policies.kyverno.io/severity: medium policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >- policies.kyverno.io/description: >-
Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. The sidecar port defaults to 9001 and can be overridden via the policies.forteapps.io/auth-port annotation. A NetworkPolicy is generated to restrict ingress to the sidecar port only. Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. A NetworkPolicy is generated to restrict ingress to the sidecar port only.
spec: spec:
background: false background: false
rules: rules:
@@ -119,26 +119,21 @@ spec:
- name: appPort - name: appPort
variable: variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate: mutate:
patchStrategicMerge: patchStrategicMerge:
spec: spec:
containers: containers:
- name: authn - name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
ports: ports:
- containerPort: "{{ sidecarPort }}" - containerPort: 8080
name: auth name: auth
protocol: TCP protocol: TCP
env: env:
- name: AUTH_LISTEN_ADDR - name: AUTH_LISTEN_ADDR
value: ":{{ sidecarPort }}" value: ":8080"
- name: AUTH_UPSTREAM_URL - name: AUTH_UPSTREAM_URL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}"
- name: AUTH_PUBLIC_PATHS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
- name: AUTH_TOKEN_FILE - name: AUTH_TOKEN_FILE
value: "/etc/auth/tokens" value: "/etc/auth/tokens"
- name: AUTH_MODE - name: AUTH_MODE
@@ -157,13 +152,13 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: "{{ sidecarPort }}" port: 8080
initialDelaySeconds: 2 initialDelaySeconds: 2
periodSeconds: 5 periodSeconds: 5
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: "{{ sidecarPort }}" port: 8080
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
securityContext: securityContext:
@@ -200,25 +195,22 @@ spec:
- name: appPort - name: appPort
variable: variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate: mutate:
patchStrategicMerge: patchStrategicMerge:
spec: spec:
containers: containers:
- name: authn - name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: "{{ sidecarPort }}" - containerPort: 8080
name: auth name: auth
protocol: TCP protocol: TCP
env: env:
- name: AUTH_MODE - name: AUTH_MODE
value: "oidc" value: "oidc"
- name: AUTH_LISTEN_ADDR - name: AUTH_LISTEN_ADDR
value: ":{{ sidecarPort }}" value: ":8080"
- name: AUTH_LOG_LEVEL - name: AUTH_LOG_LEVEL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}"
- name: AUTH_UPSTREAM_URL - name: AUTH_UPSTREAM_URL
@@ -233,8 +225,6 @@ spec:
value: "{{ regex_replace_all('https?://[^/]*', request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-callback-path\", '') }}" value: "{{ regex_replace_all('https?://[^/]*', request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-callback-path\", '') }}"
- name: AUTH_OIDC_SCOPES - name: AUTH_OIDC_SCOPES
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-scopes\" || 'openid,profile,email' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-scopes\" || 'openid,profile,email' }}"
- name: AUTH_PUBLIC_PATHS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
- name: AUTH_OIDC_COOKIE_SECRET - name: AUTH_OIDC_COOKIE_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -243,8 +233,8 @@ spec:
- name: AUTH_OIDC_CLIENT_SECRET - name: AUTH_OIDC_CLIENT_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}" name: auth-oidc
key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}" key: client-secret
resources: resources:
limits: limits:
cpu: 50m cpu: 50m
@@ -255,13 +245,13 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: "{{ sidecarPort }}" port: 8080
initialDelaySeconds: 2 initialDelaySeconds: 2
periodSeconds: 5 periodSeconds: 5
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: "{{ sidecarPort }}" port: 8080
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
securityContext: securityContext:
@@ -293,25 +283,22 @@ spec:
- name: appPort - name: appPort
variable: variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate: mutate:
patchStrategicMerge: patchStrategicMerge:
spec: spec:
containers: containers:
- name: authn - name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: "{{ sidecarPort }}" - containerPort: 8080
name: auth name: auth
protocol: TCP protocol: TCP
env: env:
- name: AUTH_MODE - name: AUTH_MODE
value: "mcp" value: "mcp"
- name: AUTH_LISTEN_ADDR - name: AUTH_LISTEN_ADDR
value: ":{{ sidecarPort }}" value: ":8080"
- name: AUTH_LOG_LEVEL - name: AUTH_LOG_LEVEL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}"
- name: AUTH_UPSTREAM_URL - name: AUTH_UPSTREAM_URL
@@ -320,10 +307,8 @@ spec:
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-resource\" }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-resource\" }}"
- name: AUTH_MCP_AUTHORIZATION_SERVERS - name: AUTH_MCP_AUTHORIZATION_SERVERS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-authority\" }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-authority\" }}"
- name: AUTH_PUBLIC_PATHS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
- name: AUTH_MCP_SCOPES_SUPPORTED - name: AUTH_MCP_SCOPES_SUPPORTED
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'profile' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'read,write' }}"
resources: resources:
limits: limits:
cpu: 50m cpu: 50m
@@ -334,106 +319,13 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: "{{ sidecarPort }}" port: 8080
initialDelaySeconds: 2 initialDelaySeconds: 2
periodSeconds: 5 periodSeconds: 5
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: "{{ sidecarPort }}" port: 8080
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
- name: inject-sidecar-oauth
skipBackgroundRequests: true
match:
any:
- resources:
kinds:
- Pod
annotations:
policies.forteapps.io/auth: "true"
policies.forteapps.io/auth-type: "oauth"
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
- argocd
- cert-manager
- monitoring
context:
- name: appPort
variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate:
patchStrategicMerge:
spec:
containers:
- name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
imagePullPolicy: Always
ports:
- containerPort: "{{ sidecarPort }}"
name: auth
protocol: TCP
env:
- name: AUTH_MODE
value: "oauth"
- name: AUTH_LISTEN_ADDR
value: ":{{ sidecarPort }}"
- name: AUTH_LOG_LEVEL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}"
- name: AUTH_UPSTREAM_URL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}"
- name: AUTH_OAUTH_AUTHORITY
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-authority\" }}"
- name: AUTH_OAUTH_CLIENT_ID
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-client-id\" }}"
- name: AUTH_OAUTH_SCOPES
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-scopes\" || 'openid,profile,email' }}"
- name: AUTH_OAUTH_DELEGATION_ENABLED
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-enabled\" || 'false' }}"
- name: AUTH_OAUTH_DELEGATION_CLIENT_ID
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-client-id\" || '' }}"
- name: AUTH_OAUTH_DELEGATION_SCOPES
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-scopes\" || '' }}"
- name: AUTH_OAUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret\" || 'auth-oauth' }}"
key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret-key\" || 'client-secret' }}"
- name: AUTH_OAUTH_DELEGATION_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: auth-oauth
key: delegation-client-secret
resources:
limits:
cpu: 50m
memory: 64Mi
requests:
cpu: 10m
memory: 32Mi
readinessProbe:
httpGet:
path: /healthz
port: "{{ sidecarPort }}"
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: "{{ sidecarPort }}"
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
securityContext: securityContext:
@@ -466,10 +358,6 @@ spec:
operator: In operator: In
value: value:
- CREATE - CREATE
context:
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
generate: generate:
synchronize: false synchronize: false
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
@@ -488,5 +376,5 @@ spec:
- Ingress - Ingress
ingress: ingress:
- ports: - ports:
- port: "{{ sidecarPort }}" - port: 8080
protocol: TCP protocol: TCP

View File

@@ -0,0 +1,71 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-deployment-owner
spec:
validationFailureAction: Audit
background: false
rules:
- name: check-pod-owner-is-replicaset-from-deployment
skipBackgroundRequests: true
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
- cert-manager
- monitoring
- argocd
- traefik-system
context:
- name: ownerReplicaSet
apiCall:
method: GET
urlPath: "/apis/apps/v1/namespaces/{{request.namespace}}/replicasets/{{request.object.metadata.ownerReferences[0].name}}"
jmesPath: "@"
preconditions:
all:
- key: "{{ request.object.metadata.ownerReferences || `[]` | [?kind=='ReplicaSet'] | length(@) }}"
operator: GreaterThanOrEquals
value: 1
validate:
allowExistingViolations: true
message: "Pods must be created through a Deployment resource."
deny:
conditions:
any:
- key: "{{ownerReplicaSet.metadata.ownerReferences[0].kind}}"
operator: NotEquals
value: Deployment
- name: deny-pods-without-replicaset-owner
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
- cert-manager
- monitoring
- argocd
- traefik-system
skipBackgroundRequests: true
validate:
allowExistingViolations: true
message: "Direct pod creation is not allowed. Pods must come from a Deployment managed by ArgoCD."
deny:
conditions:
all:
- key: "{{ request.object.metadata.ownerReferences || `[]` | [?kind=='ReplicaSet'] | length(@) }}"
operator: LessThan
value: 1

View File

@@ -1,37 +0,0 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: keycloak-client-config-cloner
spec:
rules:
- name: clone-client-config-to-keycloak
skipBackgroundRequests: false
match:
any:
- resources:
kinds:
- Secret
selector:
matchLabels:
keycloak.forteapps.net/client-config: "true"
exclude:
any:
- resources:
namespaces:
- keycloak
generate:
apiVersion: v1
kind: Secret
name: "{{request.object.metadata.name}}"
namespace: keycloak
synchronize: true
data:
metadata:
labels:
keycloak.forteapps.net/client-config: "true"
keycloak.forteapps.net/source-namespace: "{{request.object.metadata.namespace}}"
annotations:
keycloak.forteapps.net/source-name: "{{request.object.metadata.name}}"
keycloak.forteapps.net/source-namespace: "{{request.object.metadata.namespace}}"
data: "{{request.object.data}}"
type: "{{request.object.type}}"

View File

@@ -26,7 +26,6 @@ spec:
- monitoring - monitoring
- secrets - secrets
- kyverno - kyverno
- trivy-system
match: match:
any: any:
- resources: - resources:

View File

@@ -0,0 +1,20 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: snothub-repo-creds
namespace: argocd
spec:
encryptedData:
sshPrivateKey: AgCBd+i8jXBDwvWM0YC9OWvNTKyynVpW6hF0F3aH0GXBIYxzFo1g9kMajE/Ce3bTl8DiVs7VGzPdI6lmJtSC3+fVMm4wavDGrUUbhUHSR/HnEqq51NVjxU1Uj+VRz550z9r6gB0VLAcYqN0WLKd/4Qn7tvQOmKsaXMd5jsIYpSB8nZK87awZ5niFmT8DIKu2DOzoDBeN7Yqrf1aELq3m3kaDxGcDMYSglB0xRU3fF8FYr5inCic1eTrCUdfIwnrBSublNxI9FrGnlylRC40XCPxNpy6zyoQt7yeJTNgRSvtfceqXAwh25mcvga+BfDBTLPQ0EDbCoNDnE9EyMUa4kWcXBTFZ1Qx5NGRz8HAjWoYDbuNOonl9ik5dvv9UKKA+/04ZEuPjsbdkBq9xKuowx0zLL1IVv/jeuViFdvNY6LKNu/hLwMn8aMlTLlIkB+1fsIqV7Vkva9Vk7IXNz575lMwIGUTo1dsK9FQ5+uIe2bsRnY8RJ3lpyndZQ1HDPh7P7KZLZPH8fUuAHH1UF74njMQyve79zCRcisBAewpXdq4UsYAYUQOluS1Ak+sFcIdQ0jHjfklGCcJnTvMyO7obIsPQSv39/bsCqQX6uisrTzcb0s7wnbzhcxf1gm7IyZMIhi2Vub3GoLCIMnb6ViO8k+itLUa5eZpoEeg+BpP1mgL7O2nVfrZYgueULgMSvN89ct/THITsFAR8614An3DCHSnZLv5ZmY7yC6rmO405IlrnjvfFqEt9MdqTgR7uTYDSdI7UjIFJp3rchzikF1pSDMu/siKmD/Vi9+S0KqBcSENz7EUppVuYIk0aRBqHZ9Awoe0qPIjp1AMg947FjIYXzkGk3Kz0P85fGwkktyfHNZdIrXvpDAIP2739Mr7Hde+EHpuajrhuBgozfless5PqLFfGWFnt5COW2HrdSrvrY56MXuJUfiV0nq3eEhCju9f/cA41VbxfO0Hj+KMCjxiL+MCgIt1eWD24P7GQHb0D7+JiuwqgcH0ZqXRaKFYSOJ3/U1o9RRX9v5yebNTm8ErQTSnIN5bNRgE+t8CrTulGVcpaL/rW+XW8cO8MXLC/R4eeNwVjoiK4yXSHpSjf5dF9MZ6bS6SSZPMOa2besEzzQIj0c/EkUsp/GmIF1utDbJVI2VZLFGklwRo92OJs3URXNCqbzzgte43Y9dJp2VdWyi3Zx32pXVWaNdOzeifVOASP7ags7Kbdfvoaa5CmTu17iRUHCOv6164/KFeDCfY=
type: AgCIKe+SzLHIp+6gjOVbD7wcpZkeg5UwgXabFjtonavkPbzX+txHWN7IH2HbDmd7TdpgGFiqMGFSQC8mOVXnj+Pw1XnI8trH6wavvKjQ1SsRWteB8o7lGe2PcG2h2v5yW2vk5rrmuB0ehogeJez3ynlk508HguRtidzxnKdZyc6SHOh2hbWq1clspJicREsHlz3Rfn7upOSUyFmx+Tilfnjuom5FFYGBNZt0aEjaW5S7fjcYJBTEerjGjTz3vUs2DK3C5ymyCKKasA628fVZ20uIZhmn5NUGGK6bKDusFOQhYjRnxRYwS2fToBHiVfC8wo7bWW8ZwOrbguvedJU2q9pwjKvXy9upw8Ra3EcYzXASqwI6rqUQv6htGqzTYtVkRsdVxaQqITc3FYrhKIroru4Iup4xYtcVojppz0+HQxiv5WtxosVtfXXX4Zj4spr9ThoZgzLe3ebILEIK3wJNmK/AxV80NXq7JOI6gaiLK/fbm7gd9G7oW9VuM50OJELDmR4jU2k1KSx6sD5lB+c+Ajp1iw+iS9ETD/je93+eyBRKGM9fqP0+DpNsJBBlVLuWK/BwidBN186pFi4DlMzo13Wd0zYKQce4NVrf5s4f7T01KeBU+nnvvcuI+rW7tsY3R18zgP9D9uX2E5066qJQdrFov2YuXwovS4hCQDttIfz9YoGUKKQ3PObWUVynhtBptoGbhRw=
url: AgCjVnAWNarZplxbAurz++eBiKXIuYUcoleDcsrMdwdflBOOf6ayguCr7CDHzUbuKFnlNKoFvIvjUOqFb1v5Yy4iWju3ajvlo+ncEIezxetYOmQSft3nSeOD3+RCZn4Qf4K2C1D113IyPCo7T2h01KuVLWOItfuWuVwFDpLTvmKfTRAs8eP1L4ohvAcY0/J5mAmi7tXV2hN1r9R2MEuvHOusujtrRqewXHhzBksSb9/wxilruU/BHQAhYKeKHzi7QoIOeXJTnGEYxuTvskwKQhschIOIBPAOLaUbgKkuHuDIf8y1Gv2b8ENu5uNvTb/ZD43jtmx9P4pS+Hwc7OW411TrkRO7XbV7qo/PqYGYpkKYDK4g7ONGnzrsmXbSmip34vXll/jAknY61QQ6D6JbbONw/psX72p+ZFvOedhlKbHRuUnDYXyQgKFWBODLb63RaMYai79qbv8mJcwDLJaPXYXpwLumHeZX91uPPjahxfvOe8VoryTpvHIbxJO85VJJ1q+7uyFy6h7LVLhifYSbb+M55p/e5Ds5gNcgUv6npPUHdbf0yjYbT1tXGjhaqk4Tx93WoOKPQ/j+nPB41akooY0YfL/ZTDzr2iCMByrx/uPQz3JE+m7VrH5BZyYjj9sSASVsULabJGFiSuGpD+u/lcUNJW+WHQetMEU7+wNo/WB3E5iG9J8qPSPFcTiTslEa8cvGPNtNgyCQ2PphlinscQsvcAQ+ALtEd1dWXPhIGbF8udK6Wx6NYt8=
template:
metadata:
creationTimestamp: null
labels:
argocd.argoproj.io/secret-type: repository
name: snothub-repo-creds
namespace: argocd
type: Opaque

View File

@@ -9,9 +9,7 @@
- [Updating an Existing Application](#updating-an-existing-application) - [Updating an Existing Application](#updating-an-existing-application)
- [Working with Secrets](#working-with-secrets) - [Working with Secrets](#working-with-secrets)
- [Enabling Authentication for Applications](#enabling-authentication-for-applications) - [Enabling Authentication for Applications](#enabling-authentication-for-applications)
- [Adding a New Keycloak Client](#adding-a-new-keycloak-client)
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
- [Documentation](#documentation)
- [Best Practices](#best-practices) - [Best Practices](#best-practices)
--- ---
@@ -91,21 +89,21 @@ If you do need cluster access, install:
You'll need read/write access to these repositories: You'll need read/write access to these repositories:
1. **launchpad** (Config repo) 1. **sturdy-adventure** (Config repo)
```bash ```bash
git clone https://git.forteapps.net/Forte/launchpad.git git clone https://github.com/fortedigital/sturdy-adventure.git
cd launchpad cd sturdy-adventure
``` ```
2. **helm-values** (Values repo) 2. **helm-values** (Values repo)
```bash ```bash
git clone https://git.forteapps.net/Forte/helm-prod-values.git git clone git@github.com:fortedigital/helm-values.git
cd helm-values cd helm-values
``` ```
3. **forte-helm** (Chart repo - read-only for most developers) 3. **forte-helm** (Chart repo - read-only for most developers)
```bash ```bash
git clone https://git.forteapps.net/Forte/forte-helm.git git clone https://github.com/snothub/forte-helm.git
cd forte-helm cd forte-helm
``` ```
@@ -134,9 +132,9 @@ mkdir -p ~/dev/k8s
cd ~/dev/k8s cd ~/dev/k8s
# Clone repositories # Clone repositories
git clone https://git.forteapps.net/Forte/launchpad.git launchpad git clone https://github.com/fortedigital/sturdy-adventure.git launchpad
git clone https://git.forteapps.net/Forte/helm-prod-values helm-prod-values git clone git@github.com:fortedigital/helm-values.git helm-prod-values
git clone https://git.forteapps.net/Forte/forte-helm forte-helm git clone https://github.com/snothub/forte-helm.git forte-helm
# Your folder structure: # Your folder structure:
# ~/dev/k8s/ # ~/dev/k8s/
@@ -203,7 +201,7 @@ Our setup uses three repositories:
|------------|---------|-----------|-----------| |------------|---------|-----------|-----------|
| **forte-helm** | Helm chart templates (generic, reusable) | Platform engineers | ❌ Rarely | | **forte-helm** | Helm chart templates (generic, reusable) | Platform engineers | ❌ Rarely |
| **helm-values** | Application configuration (image tag, env vars) | Developers / CI pipelines | ✅ Sometimes | | **helm-values** | Application configuration (image tag, env vars) | Developers / CI pipelines | ✅ Sometimes |
| **launchpad** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app | | **sturdy-adventure** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app |
### Example: Deploying "myapp" ### Example: Deploying "myapp"
@@ -238,7 +236,7 @@ app:
value: https://api.example.com value: https://api.example.com
``` ```
#### Repository: `launchpad` (ArgoCD Application) #### Repository: `sturdy-adventure` (ArgoCD Application)
```yaml ```yaml
# apps/myapp.yaml # apps/myapp.yaml
# Tells ArgoCD to deploy your app # Tells ArgoCD to deploy your app
@@ -249,7 +247,7 @@ metadata:
namespace: argocd namespace: argocd
spec: spec:
sources: sources:
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://github.com/snothub/forte-helm
path: forteapp path: forteapp
helm: helm:
valueFiles: valueFiles:
@@ -388,7 +386,7 @@ git push
### Step 3: Create ArgoCD Application Manifest ### Step 3: Create ArgoCD Application Manifest
In the `launchpad` repository, create `apps/hello-world.yaml`: In the `sturdy-adventure` repository, create `apps/hello-world.yaml`:
```yaml ```yaml
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
@@ -413,7 +411,7 @@ spec:
sources: sources:
# Source 1: Helm chart templates # Source 1: Helm chart templates
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://github.com/snothub/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
@@ -1027,7 +1025,7 @@ policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
#### Sidecar Configuration #### Sidecar Configuration
The auth sidecar container: The auth sidecar container:
- **Image**: `ghcr.io/fortedigital/auth-sidecar:latest` - **Image**: `ghcr.io/snothub/stunning-memory:latest`
- **Port**: 8080 - **Port**: 8080
- **Resources**: 10m CPU / 32Mi memory (requests), 50m CPU / 64Mi memory (limits) - **Resources**: 10m CPU / 32Mi memory (requests), 50m CPU / 64Mi memory (limits)
- **Health checks**: `/healthz` endpoint - **Health checks**: `/healthz` endpoint
@@ -1249,202 +1247,6 @@ kubectl logs -n myapp <pod-name> -c authn
--- ---
## Adding a New Keycloak Client
There are two ways to add an OIDC client, depending on your use case:
| Method | Best for | Who edits the infra repo? |
|--------|----------|--------------------------|
| **Self-service** (recommended) | New apps that deploy their own resources | App developer — no infra changes needed |
| **Legacy (realm JSON)** | Existing clients already defined in forte-realm.json (e.g., Gitea) | Platform engineer |
Both methods are served by the **Keycloak Client Registrar** CronJob, which runs every 2 minutes.
### Self-Service OIDC Client Registration
This is the recommended flow for new applications. Your app deploys a labeled config Secret in its own namespace; the platform handles everything else.
#### How It Works
1. You deploy a Secret with label `keycloak.forteapps.net/client-config: "true"` containing a `client.json` definition
2. A **Kyverno ClusterPolicy** (`keycloak-client-config-cloner`) clones it to the `keycloak` namespace
3. The **Client Registrar CronJob** picks it up within 2 minutes:
- Registers (or updates) the client in Keycloak
- Fetches the auto-generated client secret
- Creates a credential Secret in your app's namespace
- Annotates the config Secret with sync status
#### Step 1: Create the Config Secret
Deploy this Secret in your application's namespace (e.g., as part of your Helm chart or Kustomize overlay):
```yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-client-myapp
namespace: myapp
labels:
keycloak.forteapps.net/client-config: "true"
stringData:
client.json: |
{
"clientId": "myapp",
"name": "My Application",
"redirectUris": ["https://myapp.forteapps.net/*"],
"webOrigins": ["https://myapp.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"protocolMappers": [],
"secret": {
"namespace": "myapp",
"name": "myapp-oidc-credentials",
"keys": { "clientId": "client-id", "clientSecret": "client-secret" }
}
}
```
**`client.json` fields**:
| Field | Required | Description |
|-------|----------|-------------|
| `clientId` | Yes | Keycloak client ID |
| `name` | Yes | Display name in Keycloak |
| `redirectUris` | Yes | Allowed redirect URIs |
| `webOrigins` | Yes | Allowed web origins (CORS) |
| `defaultClientScopes` | No | Scopes (default: `["openid", "email", "profile"]`) |
| `protocolMappers` | No | Custom claim mappers (default: `[]`) |
| `secret.namespace` | No | Namespace for the credential Secret (default: source namespace) |
| `secret.name` | No | Name of the credential Secret (default: `<clientId>-oidc-credentials`) |
| `secret.keys.clientId` | No | Key name for client ID in credential Secret (default: `client-id`) |
| `secret.keys.clientSecret` | No | Key name for client secret in credential Secret (default: `client-secret`) |
#### Step 2: Reference the Credential Secret
In your application's deployment config, reference the credential Secret that the registrar creates:
```yaml
env:
- name: OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-id
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-secret
```
#### Step 3: Deploy and Wait
Commit and push your changes. The credential Secret will appear within 2 minutes:
```bash
# Watch for the credential Secret to be created
kubectl get secret myapp-oidc-credentials -n myapp -w
# Check registrar logs
kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
# Check sync status on the config Secret
kubectl get secret keycloak-client-myapp -n keycloak -o jsonpath='{.metadata.annotations}'
```
#### Change Detection
The registrar computes a SHA-256 hash of `client.json` and stores it as an annotation. On subsequent runs, it skips processing if:
- The hash hasn't changed, AND
- The credential Secret already exists in the target namespace
To force a re-sync, update any field in `client.json` (e.g., add a trailing space to `name`).
### Legacy Method: Realm JSON
Existing clients (like Gitea) are defined directly in `forte-realm.json` inside `keycloak-values.yaml`. The registrar syncs their secrets via client attributes.
#### Step 1: Add Client to Realm Config
In `infra/values/base/keycloak-values.yaml`, add a new entry to the `clients` array in `forte-realm.json`:
```json
{
"clientId": "myapp",
"name": "My Application",
"enabled": true,
"protocol": "openid-connect",
"clientAuthenticatorType": "client-secret",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"publicClient": false,
"redirectUris": ["https://myapp.forteapps.net/*"],
"webOrigins": ["https://myapp.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"attributes": {
"k8s.secret.sync": "true",
"k8s.secret.namespace": "myapp",
"k8s.secret.name": "myapp-oidc-credentials",
"k8s.secret.client-id-key": "key",
"k8s.secret.client-secret-key": "secret"
}
}
```
**Important**:
- Do **NOT** include a `"secret"` field — Keycloak generates one automatically
- The `attributes` block tells the registrar where to create the K8s Secret
- Set `client-id-key` / `client-secret-key` to match what the consuming app expects (defaults: `client-id` / `client-secret`)
#### Step 2: Reference the Secret in Your Application
```yaml
existingSecret: myapp-oidc-credentials
```
#### Step 3: Commit and Push
```bash
cd ~/dev/k8s/launchpad
git add infra/values/base/keycloak-values.yaml
git commit -m "Add myapp Keycloak client with auto-sync"
git push
```
ArgoCD will sync the Keycloak config, and the registrar CronJob will pick up the new client within 2 minutes.
#### Legacy Sync Attribute Reference
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `k8s.secret.sync` | Yes | — | Set to `"true"` to enable syncing |
| `k8s.secret.namespace` | Yes | — | Target K8s namespace for the secret |
| `k8s.secret.name` | Yes | — | Name of the K8s Secret to create |
| `k8s.secret.client-id-key` | No | `client-id` | Field name for the client ID in the K8s Secret |
| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for the client secret in the K8s Secret |
### Retrieving Secrets for External Deployments
The registrar always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster:
```bash
# View the central copy
kubectl get secret gitea-oidc-credentials -n secrets -o yaml
# Extract the client secret for use elsewhere
kubectl get secret myapp-oidc-credentials -n secrets \
-o jsonpath='{.data.client-secret}' | base64 -d
```
### Registrar Behavior Notes
- The registrar runs as a CronJob every 2 minutes (`concurrencyPolicy: Forbid`)
- If the target namespace doesn't exist, the target write is skipped with a warning (the central copy still happens)
- A central copy is **always** written to the `secrets` namespace for every synced client
- The registrar uses the `keycloak-credentials` secret for admin authentication
- Created secrets have the label `app.kubernetes.io/managed-by: keycloak-client-registrar`
---
## Troubleshooting ## Troubleshooting
### Application Not Deploying ### Application Not Deploying
@@ -1628,47 +1430,6 @@ If you're stuck:
--- ---
## Documentation
This repository's documentation is built with [MkDocs](https://www.mkdocs.org/) using the [Material](https://squidfund.github.io/mkdocs-material/) theme and published automatically to Gitea Pages.
### Viewing the Docs
The live documentation site is available at:
**https://git.forteapps.net/Forte/launchpad/pages/**
### Editing Documentation
All documentation source files live in the `docs/` directory as Markdown. To make changes:
1. Edit the relevant `.md` file in `docs/`
2. Commit and push to `main`
3. The Gitea Actions workflow automatically rebuilds and deploys the site
### Local Preview
To preview documentation changes locally before pushing:
```bash
# Install dependencies (one-time)
pip install mkdocs mkdocs-material
# Start the local dev server
mkdocs serve
```
Then open `http://127.0.0.1:8000` in your browser. The server live-reloads on file changes.
### How It Works
- **Workflow**: `.gitea/workflows/docs.yaml` triggers on pushes to `main` that change `docs/**`, `mkdocs.yml`, `Dockerfile.docs`, or `nginx.conf`
- **Build**: Installs MkDocs + Material theme, runs `mkdocs build`
- **Deploy**: Force-pushes the built `site/` directory to the `gitea-pages` branch
- **Serve**: Gitea Pages serves the static site from the `gitea-pages` branch
---
## Best Practices ## Best Practices
### Development Workflow ### Development Workflow
@@ -1818,4 +1579,4 @@ Now that you understand the basics:
- Docs: [Full documentation index](README.md) - Docs: [Full documentation index](README.md)
- Help: Contact platform team - Help: Contact platform team
**Last Updated**: 2026-04-16 **Last Updated**: 2026-03-16

View File

@@ -16,12 +16,12 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
### Key Characteristics ### Key Characteristics
- **Environment**: Production (internal use only) - **Environment**: Production (internal use only)
- **Cluster Type**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays - **Cluster Type**: Single cluster, single environment
- **GitOps Tool**: ArgoCD - **GitOps Tool**: ArgoCD
- **Deployment Pattern**: App-of-Apps - **Deployment Pattern**: App-of-Apps
- **Secret Management**: Sealed Secrets (kubeseal) - **Secret Management**: Sealed Secrets (kubeseal)
- **Ingress**: Traefik with Let's Encrypt TLS - **Ingress**: Traefik with Let's Encrypt TLS
- **Monitoring**: Prometheus + Grafana + Loki + Tempo + Fluent-Bit - **Monitoring**: Prometheus + Grafana + Loki + Fluent-Bit
- **Policy Engine**: Kyverno - **Policy Engine**: Kyverno
- **Notifications**: Slack integration for sync status - **Notifications**: Slack integration for sync status
@@ -53,8 +53,8 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Config Repository │ │ Config Repository │
│ (ArgoCD Applications) │ │ (ArgoCD Applications) │
│ git.forteapps.net/Forte/ │ github.com/snothub/
launchpad sturdy-adventure
└────────────────────────────────┘ └────────────────────────────────┘
@@ -62,8 +62,8 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Kubernetes Clusters │ Kubernetes Cluster
│ (UpCloud: upc-dev, upc-prod) │ (UpCloud Managed)
│ │ │ │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ ArgoCD │ │ │ │ ArgoCD │ │
@@ -83,7 +83,6 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
│ │ - Prometheus │ │ │ │ - Prometheus │ │
│ │ - Grafana │ │ │ │ - Grafana │ │
│ │ - Loki │ │ │ │ - Loki │ │
│ │ - Tempo │ │
│ │ - Fluent-Bit │ │ │ │ - Fluent-Bit │ │
│ └──────────────────────────┘ │ │ └──────────────────────────┘ │
│ │ │ │
@@ -109,82 +108,87 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
## Repository Structure ## Repository Structure
### 1. **Config Repository** (Current Repo) ### 1. **Config Repository** (Current Repo)
**Repository**: `https://git.forteapps.net/Forte/launchpad` **Repository**: `https://github.com/fortedigital/sturdy-adventure.git`
**Purpose**: GitOps configuration - ArgoCD Applications and cluster resources **Purpose**: GitOps configuration - ArgoCD Applications and cluster resources
**Location**: `C:\dev\k8s\launchpad` **Location**: `C:\dev\k8s\launchpad`
``` ```
launchpad/ sturdy-adventure/
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev cluster) ├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern)
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod cluster)
├── infra/ # Infrastructure ArgoCD Applications (Kustomize) ├── infra/ # Infrastructure ArgoCD Applications
│ ├── base/ # Base Application manifests (upc-dev defaults) │ ├── enterprise-apps.yaml # Parent app managing all apps in apps/
│ ├── kustomization.yaml │ ├── cluster-resources-application.yaml
│ ├── traefik-application.yaml │ ├── traefik-application.yaml
│ ├── keycloak.yaml │ ├── cert-manager-application.yaml
│ ├── grafana.yaml │ ├── kyverno.yaml
│ ├── gitea.yaml │ ├── kyverno-policies.yaml
│ ├── gitea-actions.yaml │ ├── prometheus.yaml
│ ├── tempo.yaml │ ├── grafana.yaml
│ ├── renovate.yaml │ ├── loki.yaml
│ ├── ... # All other Application manifests │ ├── fluent-bit.yaml
│ └── secrets.yaml ├── trivy.yaml
│ ├── overlays/ # Per-cluster overrides │ ├── sealedsecrets.yaml
│ ├── upc-dev/ # UpCloud Dev (uses base as-is) │ ├── secrets.yaml
│ │ └── upc-prod/ # UpCloud Prod (patches value paths)
│ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides for infra │ └── values/ # Helm value overrides for infra
│ ├── base/ # Shared values (all clusters) │ ├── argocd-values.yaml
│ ├── traefik-values.yaml ├── prometheus-values.yaml
│ ├── keycloak-values.yaml ├── grafana-values.yaml
│ ├── grafana-values.yaml ├── loki-values.yaml
│ ├── prometheus-values.yaml └── fluent-bit-values.yaml
│ │ ├── gitea-values.yaml
│ │ └── ...
│ ├── upc-dev/ # upc-dev cluster-specific values
│ │ ├── traefik-values.yaml
│ │ ├── keycloak-values.yaml
│ │ └── grafana-values.yaml
│ └── upc-prod/ # upc-prod cluster-specific values
│ ├── traefik-values.yaml
│ ├── keycloak-values.yaml
│ └── grafana-values.yaml
├── apps/ # Business Application ArgoCD manifests (Kustomize) ├── apps/ # Business Application ArgoCD manifests
│ ├── base/ # Base app manifests │ ├── mcp10x.yaml # MCP 10X application
│ ├── kustomization.yaml │ ├── musicman.yaml # Music Man application
│ ├── dot-ai-stack.yaml │ ├── dot-ai-stack.yaml # Dot AI Stack
│ └── ... │ └── argo-mcp.yaml # ArgoCD MCP server
│ └── overlays/
│ ├── upc-dev/ # Uses base as-is
│ └── upc-prod/ # Patches value paths
├── cluster-resources/ # Cluster-wide Kubernetes resources ├── cluster-resources/ # Cluster-wide Kubernetes resources
│ ├── ... │ ├── cert-manager-namespace.yaml
│ ├── secrets-namespace.yaml
│ ├── letsencrypt-issuer.yaml # Let's Encrypt ClusterIssuer
│ ├── kyverno-config.yaml
│ ├── argocd-notifications-secret-sealed.yaml
│ ├── snothub-repo-credentials-sealed.yaml
│ ├── forte10x-repo-credentials-sealed.yaml
│ ├── mcp10x-repo-credentials-sealed.yaml
│ └── policies/ # Kyverno policies │ └── policies/ # Kyverno policies
│ ├── deployment-verifier.yaml
│ ├── label-checker.yaml
│ ├── bare-pod-cleaner.yaml
│ ├── replicaset-cleaner.yaml
│ ├── default-ns-blocker.yaml
│ ├── secret-cloner.yaml
│ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed, per-cluster) ├── secrets/ # Application secrets (sealed)
── upc-dev/ # Secrets for upc-dev cluster ── argocd-mcp-credentials.yaml
│ ├── dot-ai-secrets.yaml
│ ├── mcp10x-credentials-sealed.yaml
│ └── musicman-credentials.yaml
├── private/ # Local-only files (NOT in Git) ├── private/ # Local-only files (NOT in Git)
│ ├── *.yaml # Unsealed secrets
│ └── *.sh # Helper scripts
└── docs/ # Documentation └── docs/ # Documentation
├── GITOPS-ARCHITECTURE.md # This file
├── DEVELOPER-GUIDE.md
├── OPERATIONS-RUNBOOK.md
└── REFERENCE.md
``` ```
**Key Points**: **Key Points**:
- `_app-of-apps-upc-dev.yaml` and `_app-of-apps-upc-prod.yaml` are the per-cluster root Applications - `_app-of-apps.yaml` is the root Application that ArgoCD monitors
- Kustomize overlays in `infra/overlays/` render base Applications with per-cluster patches - `infra/enterprise-apps.yaml` auto-discovers all apps in `apps/` folder
- Helm values are split: `values/base/` (shared) + `values/upc-dev/` or `values/upc-prod/` (cluster-specific)
- `apps/` follows the same base/overlays pattern for business applications
- Changes pushed to this repo trigger automatic syncs in ArgoCD - Changes pushed to this repo trigger automatic syncs in ArgoCD
- `private/` folder contains local-only files (Git-ignored) - `private/` folder contains local-only files (Git-ignored)
--- ---
### 2. **Helm Charts Repository** ### 2. **Helm Charts Repository**
**Repository**: `https://github.com/fortedigital/forte-helm` **Repository**: `https://github.com/snothub/forte-helm`
**Purpose**: Reusable Helm chart templates for Forte applications **Purpose**: Reusable Helm chart templates for Forte applications
**Location**: `C:\dev\k8s\forte-helm` **Location**: `C:\dev\k8s\forte-helm`
@@ -289,7 +293,7 @@ app-repository/
### The App-of-Apps Pattern ### The App-of-Apps Pattern
``` ```
_app-of-apps-{upc-dev,upc-prod}.yaml (Root, per cluster) _app-of-apps.yaml (Root)
├── infrastructure-apps (manages infra/) ├── infrastructure-apps (manages infra/)
│ ├── cluster-resources-application │ ├── cluster-resources-application
@@ -298,7 +302,6 @@ _app-of-apps-{upc-dev,upc-prod}.yaml (Root, per cluster)
│ ├── kyverno │ ├── kyverno
│ ├── prometheus │ ├── prometheus
│ ├── grafana │ ├── grafana
│ ├── tempo
│ └── ... (other infra apps) │ └── ... (other infra apps)
└── enterprise-apps (manages apps/) └── enterprise-apps (manages apps/)
@@ -309,10 +312,10 @@ _app-of-apps-{upc-dev,upc-prod}.yaml (Root, per cluster)
``` ```
**How It Works**: **How It Works**:
1. Bootstrap script installs ArgoCD and applies `_app-of-apps-upc-dev.yaml` (or `upc-prod`) 1. Bootstrap script installs ArgoCD and applies `_app-of-apps.yaml`
2. ArgoCD creates the root Application which monitors the appropriate `infra/overlays/` folder 2. ArgoCD creates the root Application which monitors `infra/` folder
3. Kustomize renders base Applications with cluster-specific patches 3. Each YAML in `infra/` becomes a child Application
4. `enterprise-apps` Application monitors the cluster's `apps/overlays/` folder 4. `enterprise-apps.yaml` monitors `apps/` folder and auto-discovers applications
5. ArgoCD continuously syncs (every 60s) and auto-heals drift 5. ArgoCD continuously syncs (every 60s) and auto-heals drift
### Sync Waves & Ordering ### Sync Waves & Ordering
@@ -340,7 +343,7 @@ Applications like `mcp10x` and `musicman` use multiple sources:
```yaml ```yaml
spec: spec:
sources: sources:
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://github.com/snothub/forte-helm
path: forteapp # Helm chart templates path: forteapp # Helm chart templates
helm: helm:
valueFiles: valueFiles:
@@ -357,34 +360,6 @@ spec:
- Easy to update all apps by changing the chart - Easy to update all apps by changing the chart
- Environment-specific values isolated in separate repo - Environment-specific values isolated in separate repo
### Multi-Cluster Pattern
Kustomize overlays enable deploying the same Applications across clusters with different configurations:
```yaml
# infra/base/ contains default (upc-dev) Applications
# Helm values are layered: base + cluster-specific
valueFiles:
- $values/infra/values/base/traefik-values.yaml # Shared config
- $values/infra/values/upc-dev/traefik-values.yaml # Cluster-specific
# infra/overlays/upc-prod/kustomization.yaml patches the second valueFile
patches:
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/traefik-values.yaml
```
**Benefits**:
- Single source of truth for Application definitions
- Cluster-specific values isolated per overlay
- Easy to add new clusters by creating a new overlay
- Base values shared across all clusters reduce duplication
--- ---
## CI/CD Pipeline ## CI/CD Pipeline
@@ -427,7 +402,7 @@ jobs:
1. **Config Repo Change**: 1. **Config Repo Change**:
- Developer updates `apps/myapp.yaml` - Developer updates `apps/myapp.yaml`
- Pushes to `launchpad` repo - Pushes to `sturdy-adventure` repo
- ArgoCD detects change (60s reconciliation) - ArgoCD detects change (60s reconciliation)
- Syncs application to cluster - Syncs application to cluster
@@ -514,6 +489,7 @@ git commit -m "Add app credentials"
**Private Repository Credentials** stored as SealedSecrets: **Private Repository Credentials** stored as SealedSecrets:
```yaml ```yaml
# cluster-resources/snothub-repo-credentials-sealed.yaml
# cluster-resources/forte10x-repo-credentials-sealed.yaml # cluster-resources/forte10x-repo-credentials-sealed.yaml
``` ```
@@ -552,9 +528,8 @@ annotations:
1. **Prometheus**: Metrics collection and storage 1. **Prometheus**: Metrics collection and storage
2. **Grafana**: Metrics visualization and dashboards 2. **Grafana**: Metrics visualization and dashboards
3. **Loki**: Log aggregation 3. **Loki**: Log aggregation
4. **Tempo**: Distributed tracing (OTLP) 4. **Fluent-Bit**: Log shipping from pods to Loki
5. **Fluent-Bit**: Log shipping from pods to Loki 5. **Trivy**: Container vulnerability scanning
6. **Trivy**: Container vulnerability scanning
### Slack Notifications ### Slack Notifications
@@ -583,7 +558,7 @@ Notifications include:
**Rebuild Process**: **Rebuild Process**:
1. Provision new Kubernetes cluster 1. Provision new Kubernetes cluster
2. Clone `launchpad` repository 2. Clone `sturdy-adventure` repository
3. Run `./bootstrap.sh` 3. Run `./bootstrap.sh`
4. ArgoCD installs and syncs all applications 4. ArgoCD installs and syncs all applications
5. Manually recreate unsealed secrets and seal them 5. Manually recreate unsealed secrets and seal them

View File

@@ -51,8 +51,8 @@ kubectl get nodes
```bash ```bash
# 1. Clone config repository # 1. Clone config repository
git clone https://git.forteapps.net/Forte/launchpad git clone https://github.com/fortedigital/sturdy-adventure.git
cd launchpad cd sturdy-adventure
# 2. Set cluster name (optional) # 2. Set cluster name (optional)
export CLUSTER_NAME="prod-cluster-01" export CLUSTER_NAME="prod-cluster-01"
@@ -130,10 +130,10 @@ Generate a dedicated SSH key for ArgoCD without a passphrase (required for autom
```bash ```bash
# Generate ED25519 key (recommended - smaller and more secure) # Generate ED25519 key (recommended - smaller and more secure)
ssh-keygen -t ed25519 -C "argocd-deploy-key-launchpad" -f argocd-deploy-key -N "" ssh-keygen -t ed25519 -C "argocd-deploy-key-sturdy-adventure" -f argocd-deploy-key -N ""
# Or RSA key if ED25519 is not supported # Or RSA key if ED25519 is not supported
ssh-keygen -t rsa -b 4096 -C "argocd-deploy-key-launchpad" -f argocd-deploy-key -N "" ssh-keygen -t rsa -b 4096 -C "argocd-deploy-key-sturdy-adventure" -f argocd-deploy-key -N ""
``` ```
This creates two files: This creates two files:
@@ -148,7 +148,7 @@ This creates two files:
``` ```
2. Go to GitHub repository settings: 2. Go to GitHub repository settings:
- Navigate to: `https://git.forteapps.net/Forte/launchpad/settings/keys` - Navigate to: `https://github.com/fortedigital/sturdy-adventure/settings/keys`
- Or: Repository → Settings → Deploy keys - Or: Repository → Settings → Deploy keys
3. Click **"Add deploy key"** 3. Click **"Add deploy key"**
@@ -169,33 +169,78 @@ This creates two files:
Add the private key to ArgoCD as a repository secret: Add the private key to ArgoCD as a repository secret:
Save the following file in private/ (gitignored) folder as secret.yaml
```bash ```bash
apiVersion: v1 # Create secret for sturdy-adventure repository
kind: Secret kubectl create secret generic repo-sturdy-adventure \
metadata: --from-file=sshPrivateKey=argocd-deploy-key \
name: forte-helm-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
sshPrivateKey: |
<paste your private key here>
project: default
```
Seal the secret using `kubeseal` command
```bash
kubeseal --format=yaml \
--namespace=argocd \ --namespace=argocd \
< private/secret.yaml \ --dry-run=client -o yaml | kubectl apply -f -
> secrets/forte-helm-repo-secret-sealed.yaml
# Label it for ArgoCD to recognize
kubectl label secret repo-sturdy-adventure \
-n argocd \
argocd.argoproj.io/secret-type=repository
# Add repository annotations
kubectl annotate secret repo-sturdy-adventure \
-n argocd \
managed-by=argocd.argoproj.io
```
Alternatively, create a complete repository secret with all metadata:
```bash
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: repo-sturdy-adventure
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
annotations:
managed-by: argocd.argoproj.io
type: Opaque
stringData:
type: git
url: git@github.com:fortedigital/sturdy-adventure.git
sshPrivateKey: |
$(cat argocd-deploy-key | sed 's/^/ /')
EOF
``` ```
**Step 4: Register Repository in ArgoCD** **Step 4: Register Repository in ArgoCD**
Check in secrets/forte-helm-repo-secret-sealed.yaml and let Argo sync and create the secret. Add the repository to ArgoCD's configuration:
```bash
# Via kubectl (recommended for GitOps)
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: repo-sturdy-adventure
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
type: Opaque
stringData:
type: git
url: git@github.com:fortedigital/sturdy-adventure.git
sshPrivateKey: |
$(cat argocd-deploy-key | sed 's/^/ /')
insecure: "false"
enableLfs: "false"
EOF
# Or via ArgoCD UI
# 1. Open ArgoCD UI: kubectl port-forward svc/argocd-server -n argocd 8080:443
# 2. Navigate to: Settings → Repositories → Connect Repo
# 3. Connection Method: Via SSH
# 4. Repository URL: git@github.com:fortedigital/sturdy-adventure.git
# 5. SSH private key: Paste private key content
# 6. Click "Connect"
```
**Step 5: Verify Repository Access** **Step 5: Verify Repository Access**
@@ -207,7 +252,7 @@ kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository
# Settings → Repositories → Should show "Successful" status # Settings → Repositories → Should show "Successful" status
# Test by creating an application # Test by creating an application
kubectl apply -f _app-of-apps-upc-dev.yaml # or _app-of-apps-upc-prod.yaml kubectl apply -f _app-of-apps.yaml
# Check application sync status # Check application sync status
kubectl get applications -n argocd kubectl get applications -n argocd
@@ -227,7 +272,7 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: main targetRevision: main
path: cluster-resources path: cluster-resources
destination: destination:
@@ -273,7 +318,7 @@ rm /tmp/test-repo-access.yaml
# Add new public key to GitHub (keep old key for now) # Add new public key to GitHub (keep old key for now)
# Update Kubernetes secret # Update Kubernetes secret
kubectl create secret generic repo-launchpad \ kubectl create secret generic repo-sturdy-adventure \
--from-file=sshPrivateKey=argocd-new-key \ --from-file=sshPrivateKey=argocd-new-key \
--namespace=argocd \ --namespace=argocd \
--dry-run=client -o yaml | kubectl apply -f - --dry-run=client -o yaml | kubectl apply -f -
@@ -290,7 +335,7 @@ rm /tmp/test-repo-access.yaml
kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository
# Review deploy keys in GitHub # Review deploy keys in GitHub
# Visit: https://git.forteapps.net/Forte/launchpad/settings/keys # Visit: https://github.com/fortedigital/sturdy-adventure/settings/keys
``` ```
4. **Use Different Keys per Repository** 4. **Use Different Keys per Repository**
@@ -304,16 +349,16 @@ rm /tmp/test-repo-access.yaml
```bash ```bash
# Check if secret exists # Check if secret exists
kubectl get secret repo-launchpad -n argocd kubectl get secret repo-sturdy-adventure -n argocd
# Verify secret has correct label # Verify secret has correct label
kubectl get secret repo-launchpad -n argocd -o yaml | grep argocd.argoproj.io/secret-type kubectl get secret repo-sturdy-adventure -n argocd -o yaml | grep argocd.argoproj.io/secret-type
# Check ArgoCD application controller logs # Check ArgoCD application controller logs
kubectl logs -n argocd deployment/argocd-application-controller | grep -i "permission denied" kubectl logs -n argocd deployment/argocd-application-controller | grep -i "permission denied"
# Verify deploy key is added to GitHub # Verify deploy key is added to GitHub
# Visit: https://git.forteapps.net/Forte/launchpad/settings/keys # Visit: https://github.com/fortedigital/sturdy-adventure/settings/keys
``` ```
**Issue: "Host key verification failed"** **Issue: "Host key verification failed"**
@@ -324,7 +369,7 @@ kubectl exec -n argocd deployment/argocd-repo-server -- \
ssh-keyscan github.com >> ~/.ssh/known_hosts ssh-keyscan github.com >> ~/.ssh/known_hosts
# Or disable strict host key checking (less secure) # Or disable strict host key checking (less secure)
kubectl patch secret repo-launchpad -n argocd \ kubectl patch secret repo-sturdy-adventure -n argocd \
--type merge \ --type merge \
-p '{"stringData":{"insecure":"true"}}' -p '{"stringData":{"insecure":"true"}}'
``` ```
@@ -336,7 +381,7 @@ kubectl patch secret repo-launchpad -n argocd \
kubectl logs -n argocd deployment/argocd-repo-server kubectl logs -n argocd deployment/argocd-repo-server
# Refresh repository connection # Refresh repository connection
kubectl delete secret repo-launchpad -n argocd kubectl delete secret repo-sturdy-adventure -n argocd
# Recreate secret (see Step 3 above) # Recreate secret (see Step 3 above)
# Restart ArgoCD components # Restart ArgoCD components
@@ -346,21 +391,21 @@ kubectl rollout restart deployment argocd-application-controller -n argocd
#### Multiple Repository Setup #### Multiple Repository Setup
For the three-repository pattern (launchpad, forte-helm, helm-values): For the three-repository pattern (sturdy-adventure, forte-helm, helm-values):
```bash ```bash
# 1. launchpad (main config repo) # 1. sturdy-adventure (main config repo)
ssh-keygen -t ed25519 -C "argocd-launchpad" -f key-sturdy -N "" ssh-keygen -t ed25519 -C "argocd-sturdy-adventure" -f key-sturdy -N ""
# Add key-sturdy.pub to: https://git.forteapps.net/Forte/launchpad/settings/keys # Add key-sturdy.pub to: https://github.com/fortedigital/sturdy-adventure/settings/keys
# 2. helm-values (private values repo) # 2. helm-values (private values repo)
ssh-keygen -t ed25519 -C "argocd-helm-values" -f key-helm-values -N "" ssh-keygen -t ed25519 -C "argocd-helm-values" -f key-helm-values -N ""
# Add key-helm-values.pub to: https://github.com/fortedigital/helm-values/settings/keys # Add key-helm-values.pub to: https://github.com/fortedigital/helm-values/settings/keys
# 3. forte-helm (private helm charts repo) # 3. forte-helm is public - no key needed (use HTTPS)
# Create secrets # Create secrets
kubectl create secret generic repo-launchpad \ kubectl create secret generic repo-sturdy-adventure \
--from-file=sshPrivateKey=key-sturdy \ --from-file=sshPrivateKey=key-sturdy \
--namespace=argocd --dry-run=client -o yaml | \ --namespace=argocd --dry-run=client -o yaml | \
kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \ kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \
@@ -385,9 +430,9 @@ If you're currently using HTTPS and want to switch to SSH:
# 2. Update all Application manifests # 2. Update all Application manifests
# Change from: # Change from:
# repoURL: https://git.forteapps.net/Forte/launchpad # repoURL: https://github.com/fortedigital/sturdy-adventure.git
# To: # To:
# repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git # repoURL: git@github.com:fortedigital/sturdy-adventure.git
# 3. Update and commit # 3. Update and commit
find . -name "*.yaml" -type f -exec sed -i 's|https://github.com/fortedigital/|git@github.com:fortedigital/|g' {} + find . -name "*.yaml" -type f -exec sed -i 's|https://github.com/fortedigital/|git@github.com:fortedigital/|g' {} +
@@ -954,33 +999,6 @@ curl -G -s 'http://localhost:3100/loki/api/v1/query_range' \
--data-urlencode 'start=1h' | jq --data-urlencode 'start=1h' | jq
``` ```
### Tempo Traces
```bash
# Port forward to Tempo query API
kubectl port-forward -n monitoring svc/tempo 3200:3200
# Access: http://localhost:3200
```
**Query traces via Grafana:**
1. Open Grafana → Explore
2. Select Tempo datasource
3. Use TraceQL or search by service name
**Verify Traefik is sending traces:**
```bash
# Check Traefik logs for OTLP export errors
kubectl logs -n traefik-system -l app.kubernetes.io/name=traefik | grep -i "traces export"
# Check Tempo is receiving data
kubectl logs -n monitoring -l app.kubernetes.io/name=tempo | grep "receiver"
```
**Trace-to-log correlation:**
- Click a trace span in Grafana → linked Loki logs appear (by namespace, pod, container)
- Trace-to-metrics links to Prometheus by service name
### Fluent-Bit Log Shipping ### Fluent-Bit Log Shipping
Verify Fluent-Bit is shipping logs: Verify Fluent-Bit is shipping logs:
@@ -1352,13 +1370,13 @@ kubectl get deployment argocd-server -n argocd \
-o jsonpath='{.spec.template.spec.containers[0].image}' -o jsonpath='{.spec.template.spec.containers[0].image}'
# Update version in values # Update version in values
vim infra/values/base/argocd-values.yaml vim infra/values/argocd-values.yaml
# Or upgrade via Helm directly # Or upgrade via Helm directly
helm upgrade argocd argo-cd \ helm upgrade argocd argo-cd \
--repo https://argoproj.github.io/argo-helm \ --repo https://argoproj.github.io/argo-helm \
--namespace argocd \ --namespace argocd \
--values infra/values/base/argocd-values.yaml \ --values infra/values/argocd-values.yaml \
--version 6.0.0 # New version --version 6.0.0 # New version
# Verify # Verify
@@ -1454,8 +1472,8 @@ kubectl top pods --all-namespaces --sort-by=cpu
Example: Adding Redis Example: Adding Redis
```bash ```bash
# 1. Create application manifest in base/ # 1. Create application manifest
cat > infra/base/redis-application.yaml <<EOF cat > infra/redis-application.yaml <<EOF
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
metadata: metadata:
@@ -1465,17 +1483,15 @@ metadata:
argocd.argoproj.io/sync-wave: "1" argocd.argoproj.io/sync-wave: "1"
spec: spec:
project: default project: default
sources: source:
- repoURL: https://charts.bitnami.com/bitnami repoURL: https://charts.bitnami.com/bitnami
chart: redis chart: redis
targetRevision: 18.0.0 targetRevision: 18.0.0
helm: helm:
releaseName: redis values: |
valueFiles: auth:
- \$values/infra/values/base/redis-values.yaml enabled: true
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git password: changeme
targetRevision: HEAD
ref: values
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: redis namespace: redis
@@ -1487,37 +1503,30 @@ spec:
- CreateNamespace=true - CreateNamespace=true
EOF EOF
# 2. Add to base kustomization # 2. Commit and push
# Edit infra/base/kustomization.yaml and add: - redis-application.yaml git add infra/redis-application.yaml
# 3. Create base values file
cat > infra/values/base/redis-values.yaml <<EOF
auth:
enabled: true
EOF
# 4. Commit and push
git add infra/base/redis-application.yaml infra/values/base/redis-values.yaml infra/base/kustomization.yaml
git commit -m "Add Redis infrastructure component" git commit -m "Add Redis infrastructure component"
git push git push
# 5. ArgoCD will auto-sync within 60 seconds # 3. ArgoCD will auto-sync within 60 seconds
``` ```
### Multi-Cluster Setup ### Multi-Cluster Setup (Future)
The repository supports multiple clusters via Kustomize overlays: For multi-cluster deployments:
- **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is ```yaml
- **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod` # Different destinations per environment
# dev-cluster
destination:
server: https://dev.k8s.example.com
namespace: myapp
Each cluster has its own: # prod-cluster
- Root app-of-apps file: `_app-of-apps-upc-dev.yaml` / `_app-of-apps-upc-prod.yaml` destination:
- Cluster-specific Helm values: `infra/values/upc-dev/` / `infra/values/upc-prod/` server: https://prod.k8s.example.com
- Sealed secrets: `secrets/upc-dev/` (others as needed) namespace: myapp
- Apps overlay: `apps/overlays/upc-dev/` / `apps/overlays/upc-prod/` ```
To add a new cluster, create a new overlay directory (e.g., `infra/overlays/upc-staging/`) with patches that swap the value file paths.
### Blue-Green Deployments ### Blue-Green Deployments

View File

@@ -180,7 +180,7 @@ Reference for:
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Kubernetes Clusters (UpCloud: upc-dev, upc-prod) Kubernetes Cluster (UpCloud)
│ ┌──────────────────────────────────────────────────────┐ │ │ ┌──────────────────────────────────────────────────────┐ │
│ │ Infrastructure: Traefik, Cert-Manager, Kyverno │ │ │ │ Infrastructure: Traefik, Cert-Manager, Kyverno │ │
│ ├──────────────────────────────────────────────────────┤ │ │ ├──────────────────────────────────────────────────────┤ │
@@ -194,7 +194,7 @@ Reference for:
### Key Technologies ### Key Technologies
- **GitOps**: ArgoCD - **GitOps**: ArgoCD
- **Kubernetes**: UpCloud Managed Kubernetes (multi-cluster: upc-dev, upc-prod) - **Kubernetes**: UpCloud Managed Kubernetes
- **Ingress**: Traefik v2 - **Ingress**: Traefik v2
- **Certificates**: Cert-Manager + Let's Encrypt - **Certificates**: Cert-Manager + Let's Encrypt
- **Policies**: Kyverno - **Policies**: Kyverno

View File

@@ -21,7 +21,7 @@
|-----------|-------| |-----------|-------|
| **Provider** | UpCloud Managed Kubernetes | | **Provider** | UpCloud Managed Kubernetes |
| **Environment** | Production (internal use) | | **Environment** | Production (internal use) |
| **Cluster Count** | Multi-cluster (upc-dev, upc-prod) | | **Cluster Count** | Single cluster |
| **GitOps Tool** | ArgoCD | | **GitOps Tool** | ArgoCD |
| **Ingress Controller** | Traefik v2 | | **Ingress Controller** | Traefik v2 |
| **Certificate Management** | Cert-Manager + Let's Encrypt | | **Certificate Management** | Cert-Manager + Let's Encrypt |
@@ -29,9 +29,7 @@
| **Secret Management** | Sealed Secrets (Bitnami) | | **Secret Management** | Sealed Secrets (Bitnami) |
| **Monitoring** | Prometheus + Grafana | | **Monitoring** | Prometheus + Grafana |
| **Logging** | Loki + Fluent-Bit | | **Logging** | Loki + Fluent-Bit |
| **Tracing** | Tempo (OTLP) |
| **Container Scanning** | Trivy | | **Container Scanning** | Trivy |
| **Version Control** | Gitea |
### Network Architecture ### Network Architecture
@@ -62,17 +60,16 @@ Internet
## Repository Reference ## Repository Reference
### Config Repository: `launchpad` ### Config Repository: `sturdy-adventure`
**URL**: `https://git.forteapps.net/Forte/launchpad` **URL**: `https://github.com/fortedigital/sturdy-adventure.git`
#### Directory Structure #### Directory Structure
``` ```
launchpad/ sturdy-adventure/
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev) ├── _app-of-apps.yaml # Root ArgoCD Application
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod)
├── infra/ # Infrastructure applications ├── infra/ # Infrastructure applications
│ ├── cluster-resources-application.yaml │ ├── cluster-resources-application.yaml
@@ -84,24 +81,16 @@ launchpad/
│ ├── prometheus.yaml │ ├── prometheus.yaml
│ ├── grafana.yaml │ ├── grafana.yaml
│ ├── loki.yaml │ ├── loki.yaml
│ ├── tempo.yaml
│ ├── fluent-bit.yaml │ ├── fluent-bit.yaml
│ ├── trivy.yaml │ ├── trivy.yaml
│ ├── gitea.yaml
│ ├── gitea-actions.yaml
│ ├── sealedsecrets.yaml │ ├── sealedsecrets.yaml
│ ├── secrets.yaml │ ├── secrets.yaml
│ ├── renovate.yaml
│ └── values/ │ └── values/
│ ├── argocd-values.yaml │ ├── argocd-values.yaml
│ ├── prometheus-values.yaml │ ├── prometheus-values.yaml
│ ├── grafana-values.yaml │ ├── grafana-values.yaml
│ ├── loki-values.yaml │ ├── loki-values.yaml
── tempo-values.yaml ── fluent-bit-values.yaml
│ ├── gitea-values.yaml
│ ├── gitea-actions-values.yaml
│ ├── fluent-bit-values.yaml
│ └── renovate-values.yaml
├── apps/ # Business applications ├── apps/ # Business applications
│ ├── mcp10x.yaml │ ├── mcp10x.yaml
@@ -115,6 +104,7 @@ launchpad/
│ ├── letsencrypt-issuer.yaml │ ├── letsencrypt-issuer.yaml
│ ├── kyverno-config.yaml │ ├── kyverno-config.yaml
│ ├── argocd-notifications-secret-sealed.yaml │ ├── argocd-notifications-secret-sealed.yaml
│ ├── snothub-repo-credentials-sealed.yaml
│ ├── forte10x-repo-credentials-sealed.yaml │ ├── forte10x-repo-credentials-sealed.yaml
│ ├── mcp10x-repo-credentials-sealed.yaml │ ├── mcp10x-repo-credentials-sealed.yaml
│ └── policies/ │ └── policies/
@@ -124,14 +114,11 @@ launchpad/
│ ├── replicaset-cleaner.yaml │ ├── replicaset-cleaner.yaml
│ ├── default-ns-blocker.yaml │ ├── default-ns-blocker.yaml
│ ├── secret-cloner.yaml │ ├── secret-cloner.yaml
│ ├── keycloak-client-cloner.yaml
│ └── auth-sidecar-injector.yaml │ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed) ├── secrets/ # Application secrets (sealed)
│ ├── argocd-mcp-credentials.yaml │ ├── argocd-mcp-credentials.yaml
│ ├── dot-ai-secrets.yaml │ ├── dot-ai-secrets.yaml
│ ├── gitea-credentials-sealed.yaml
│ ├── gitea-runner-token-sealed.yaml
│ ├── mcp10x-credentials-sealed.yaml │ ├── mcp10x-credentials-sealed.yaml
│ └── musicman-credentials.yaml │ └── musicman-credentials.yaml
@@ -157,15 +144,15 @@ ArgoCd() {
helm upgrade --install argocd argo-cd \ helm upgrade --install argocd argo-cd \
--repo https://argoproj.github.io/argo-helm \ --repo https://argoproj.github.io/argo-helm \
--namespace argocd --create-namespace \ --namespace argocd --create-namespace \
--values infra/values/base/argocd-values.yaml \ --values infra/values/argocd-values.yaml \
--set notifications.context.clusterName="$CLUSTER_NAME" \ --set notifications.context.clusterName="$CLUSTER_NAME" \
--timeout 60s --atomic --timeout 60s --atomic
kubectl apply -f _app-of-apps-upc-dev.yaml -n argocd # or _app-of-apps-upc-prod.yaml kubectl apply -f _app-of-apps.yaml -n argocd
} }
``` ```
**`_app-of-apps-upc-dev.yaml`** / **`_app-of-apps-upc-prod.yaml`** **`_app-of-apps.yaml`**
```yaml ```yaml
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
@@ -175,7 +162,7 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
path: infra path: infra
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
@@ -190,7 +177,7 @@ spec:
### Helm Charts Repository: `forte-helm` ### Helm Charts Repository: `forte-helm`
**URL**: `https://github.com/fortedigital/forte-helm` **URL**: `https://github.com/snothub/forte-helm`
#### Chart: `forteapp` #### Chart: `forteapp`
@@ -526,7 +513,7 @@ spec:
# Multi-source configuration # Multi-source configuration
sources: sources:
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://github.com/snothub/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
@@ -616,7 +603,7 @@ retry:
**Configuration**: **Configuration**:
```yaml ```yaml
# infra/base/traefik-application.yaml # infra/traefik-application.yaml
replicas: 2 replicas: 2
service: service:
@@ -717,7 +704,6 @@ kubeStateMetrics:
**Datasources**: **Datasources**:
- Prometheus - Prometheus
- Loki - Loki
- Tempo
### Loki ### Loki
@@ -735,45 +721,6 @@ promtail:
enabled: false # Using Fluent-Bit instead enabled: false # Using Fluent-Bit instead
``` ```
### Tempo
**Chart**: `grafana/tempo`
**Version**: 1.24.4
**Namespace**: `monitoring`
**Purpose**: Distributed tracing backend receiving OTLP traces from Traefik and other instrumented services.
**Configuration**:
```yaml
tempo:
storage:
trace:
backend: local
local:
path: /var/tempo/traces
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
persistence:
enabled: true
size: 10Gi
```
**Endpoints**:
- gRPC OTLP receiver: `:4317`
- HTTP OTLP receiver: `:4318`
- Query API: `:3200`
**Grafana Integration**:
- Trace-to-logs correlation with Loki (by namespace, pod, container)
- Trace-to-metrics correlation with Prometheus (by service name)
- Service graph and node graph visualization
### Fluent-Bit ### Fluent-Bit
**Chart**: `fluent/fluent-bit` **Chart**: `fluent/fluent-bit`
@@ -781,286 +728,6 @@ persistence:
**Output**: Loki **Output**: Loki
### Gitea
**Chart**: `gitea/gitea`
**Version**: 12.5.0 (app v1.25.4)
**Namespace**: `gitea`
**Purpose**: Self-hosted Git repository hosting with pull requests, issues, CI/CD (Gitea Actions), container registry, and package registry.
**Configuration**:
```yaml
# infra/base/gitea.yaml + infra/values/base/gitea-values.yaml
ingress:
host: git.forteapps.net
tls: cert-manager (letsencrypt-prod)
gitea:
admin:
existingSecret: gitea-credentials
config:
service:
DISABLE_REGISTRATION: true
ALLOW_ONLY_EXTERNAL_REGISTRATION: true
actions:
ENABLED: true
packages:
ENABLED: true
metrics:
ENABLED: true
postgresql:
enabled: true
persistence: 8Gi (upcloud-block-storage-maxiops)
```
**Authentication**: Keycloak OIDC via `forte` realm (client ID: `gitea`). Protocol mapper: `email_verified` hardcoded claim (`true`, boolean) on ID token, Access token, and Userinfo.
**Endpoints**:
- Web UI: `https://git.forteapps.net`
- SSH: port 22 (ClusterIP)
- Metrics: `/metrics` (Prometheus scrape)
**Secrets**: `gitea-credentials` (SealedSecret) containing `admin-password`, `postgres-password`, `secret` (OIDC client secret)
### Gitea Actions Runners
**Chart**: `actions` (from `https://dl.gitea.com/charts`)
**Namespace**: `gitea`
**Sync Wave**: 2 (deploys after Gitea)
**Purpose**: Act runners execute Gitea Actions CI/CD workflows. Deployed as a StatefulSet with a Docker-in-Docker sidecar for container-based job execution.
**Configuration**:
```yaml
# infra/base/gitea-actions.yaml + infra/values/base/gitea-actions-values.yaml
replicaCount: 3
runner:
labels:
- "ubuntu-latest:docker://node:20-bookworm"
- "ubuntu-22.04:docker://node:20-bookworm"
existingSecret: gitea-runner-token
gitea:
instance:
url: http://gitea-http.gitea.svc.cluster.local:3000
dind:
enabled: true # Docker-in-Docker sidecar (privileged)
```
**Resources**:
| Container | CPU Request | Memory Request | CPU Limit | Memory Limit |
|-----------|-------------|----------------|-----------|--------------|
| Runner | 250m | 256Mi | 1 | 1Gi |
| DinD sidecar | 250m | 256Mi | 1 | 1Gi |
**Secrets**: `gitea-runner-token` (SealedSecret) containing `token` (instance-level runner registration token from `/admin/runners`)
**Setup Steps**:
1. Get runner registration token from Gitea admin panel (`/admin/runners`)
2. Fill in `private/gitea-runner-token.yaml` with the token
3. Seal: `kubeseal --format yaml < private/gitea-runner-token.yaml > secrets/gitea-runner-token-sealed.yaml`
4. Commit and push — ArgoCD deploys runners automatically
**Verification**:
- `kubectl get statefulset -n gitea` — 3/3 runners ready
- Gitea admin panel (`/admin/runners`) — runners show as Online
- Create test workflow in `.gitea/workflows/test.yml` — job executes
### Keycloak Client Registrar
**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`)
**Namespace**: `keycloak`
**Schedule**: `*/2 * * * *` (every 2 minutes)
**Purpose**: Handles two responsibilities:
1. **Legacy sync** — extracts secrets from Keycloak clients with `k8s.secret.sync: "true"` attribute (same as former PostSync syncer)
2. **Self-service registration** — processes config Secrets (cloned by Kyverno) to register new OIDC clients and sync their credentials
**How It Works**:
*Legacy path (existing clients like Gitea):*
1. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret
2. Queries all clients in the `forte` realm
3. Filters clients with `k8s.secret.sync: "true"` attribute
4. For each matching client, retrieves the auto-generated secret via Keycloak Admin API
5. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute)
6. Always writes a central copy to the `secrets` namespace
*Self-service path (new clients):*
1. Lists Secrets in `keycloak` namespace with label `keycloak.forteapps.net/client-config=true`
2. For each config Secret, parses `client.json` and computes a config hash
3. Skips if hash matches annotation and credential Secret already exists
4. Creates or updates the Keycloak client via Admin API
5. Fetches the generated client secret
6. Upserts credential Secret in target namespace + central `secrets` namespace
7. Annotates config Secret with sync status, config hash, and timestamp
**Resources**:
- `ServiceAccount`: `keycloak-client-registrar` (namespace: `keycloak`)
- `ClusterRole`: `keycloak-client-registrar` (secrets: get/list/create/update/patch; namespaces: get/list)
- `ClusterRoleBinding`: `keycloak-client-registrar`
- `CronJob`: `keycloak-client-registrar`
**Kyverno Policy**: `keycloak-client-config-cloner` — clones labeled Secrets from app namespaces to `keycloak` namespace (see [Kyverno Policies](#kyverno-policies))
**Legacy Client Attributes** (set in `forte-realm.json`):
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `k8s.secret.sync` | Yes | — | Set to `"true"` to enable syncing |
| `k8s.secret.namespace` | Yes | — | Target K8s namespace |
| `k8s.secret.name` | Yes | — | Name of the K8s Secret |
| `k8s.secret.client-id-key` | No | `client-id` | Field name for client ID in the Secret |
| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for client secret in the Secret |
**Self-Service Config Secret Schema**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-client-<app>
namespace: <app-namespace>
labels:
keycloak.forteapps.net/client-config: "true"
stringData:
client.json: |
{
"clientId": "<app>",
"name": "<App Name>",
"redirectUris": ["https://<app>.forteapps.net/*"],
"webOrigins": ["https://<app>.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"protocolMappers": [],
"secret": {
"namespace": "<app-namespace>",
"name": "<app>-oidc-credentials",
"keys": { "clientId": "client-id", "clientSecret": "client-secret" }
}
}
```
**Created Credential Secret Format**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: <target-name>
namespace: <target-namespace>
labels:
app.kubernetes.io/managed-by: keycloak-client-registrar
type: Opaque
data:
<client-id-key>: <base64-encoded client ID>
<client-secret-key>: <base64-encoded client secret>
```
**Config Secret Annotations** (set by registrar):
| Annotation | Description |
|-----------|-------------|
| `keycloak.forteapps.net/config-hash` | SHA-256 hash of client.json for change detection |
| `keycloak.forteapps.net/sync-status` | `synced` or `error` |
| `keycloak.forteapps.net/last-sync` | ISO 8601 timestamp of last successful sync |
**Verification**:
```bash
# Check CronJob status
kubectl get cronjobs -n keycloak
# View latest registrar logs
kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
# Verify created secret
kubectl get secret <name> -n <namespace> -o yaml
# Check config Secret annotations (self-service)
kubectl get secret keycloak-client-<app> -n keycloak -o jsonpath='{.metadata.annotations}'
```
**See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client)
### Renovate
**Chart**: `renovate` (OCI: `ghcr.io/renovatebot/charts`)
**Version**: 46.109.0 (app v43.113.0)
**Namespace**: `renovate`
**Sync Wave**: 2
**Purpose**: Automated dependency update bot. Runs as a CronJob that scans Gitea repositories for outdated dependencies and creates pull requests with updates.
**Configuration**:
```yaml
# infra/base/renovate.yaml + infra/values/base/renovate-values.yaml
cronjob:
schedule: "@daily"
concurrencyPolicy: Forbid
renovate:
config:
platform: gitea
endpoint: https://git.forteapps.net
autodiscover: true
gitAuthor: "Renovate Bot <renovate@forteapps.net>"
packageRules:
- matchRepositories: ["**/10x"]
assignees: ["edvard.unsvag"]
reviewers: ["edvard.unsvag"]
- matchRepositories: ["**/auth-sidecar"]
assignees: ["danijel.simeunovic"]
reviewers: ["danijel.simeunovic"]
- matchRepositories: ["**/forte-helm"]
assignees: ["danijel.simeunovic"]
reviewers: ["danijel.simeunovic"]
resources:
requests: { cpu: 500m, memory: 1Gi }
limits: { cpu: "2", memory: 4Gi }
```
**Note**: Assignees and reviewers are only applied at PR creation time. Existing PRs must be closed and recreated for new assignment rules to take effect.
**Secrets**: `renovate-env` (SealedSecret in `secrets` namespace, cloned by Kyverno) containing:
- `RENOVATE_TOKEN` — Gitea PAT with repo write + issue write permissions
- `RENOVATE_GITHUB_COM_TOKEN` — GitHub PAT (public_repo read-only) for changelog fetching
**Setup Steps**:
1. Fill in `private/renovate-env.yaml` with tokens
2. Seal: `kubeseal --format yaml < private/renovate-env.yaml > secrets/renovate-env-sealed.yaml`
3. Commit and push — ArgoCD deploys the CronJob, Kyverno clones the secret
**Verification**:
- `kubectl get cronjob -n renovate` — CronJob exists
- `kubectl create job --from=cronjob/renovate renovate-test -n renovate` — manual trigger
- `kubectl logs -n renovate job/renovate-test` — check logs
### Gitea Pages
**Purpose**: Hosts the MkDocs documentation site for this repository.
**How It Works**:
- A Gitea Actions workflow (`.gitea/workflows/docs.yaml`) builds MkDocs on push to `main`
- The built site is force-pushed to the `gitea-pages` branch
- Gitea serves the static site from that branch
**URL**: `https://git.forteapps.net/Forte/launchpad/pages/`
**Configuration**:
- Gitea server config: `ENABLE_GITEA_PAGES: true` (in gitea-values.yaml)
- MkDocs config: `mkdocs.yml` (repo root)
- Source files: `docs/` directory
- Theme: Material for MkDocs
**Trigger Paths**:
- `docs/**`
- `mkdocs.yml`
- `Dockerfile.docs`
- `nginx.conf`
--- ---
## Kyverno Policies ## Kyverno Policies
@@ -1097,19 +764,6 @@ spec:
**Label Requirement**: Secrets must have `allowedToBeCloned: "true"` **Label Requirement**: Secrets must have `allowedToBeCloned: "true"`
### Keycloak Client Config Cloner
**File**: `cluster-resources/policies/keycloak-client-cloner.yaml`
**Purpose**: Clones Secrets labeled `keycloak.forteapps.net/client-config: "true"` from app namespaces to the `keycloak` namespace. This allows apps to declare their OIDC client configuration in their own namespace, which the [Keycloak Client Registrar](#keycloak-client-registrar) then processes.
**Trigger**: Any Secret with label `keycloak.forteapps.net/client-config: "true"` created outside the `keycloak` namespace.
**Behavior**:
- Generates a copy of the Secret in the `keycloak` namespace with the same name
- Adds source tracking annotations (`keycloak.forteapps.net/source-namespace`, `keycloak.forteapps.net/source-name`)
- `synchronize: true` — changes to the source Secret are reflected in the clone
### Default Namespace Blocker ### Default Namespace Blocker
**File**: `cluster-resources/policies/default-ns-blocker.yaml` **File**: `cluster-resources/policies/default-ns-blocker.yaml`
@@ -1199,7 +853,7 @@ policies.forteapps.io/auth-token-secret-name: "auth-tokens"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000" policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
# Optional customization # Optional customization
policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar" policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory"
policies.forteapps.io/auth-image-version: "latest" policies.forteapps.io/auth-image-version: "latest"
``` ```
@@ -1215,7 +869,7 @@ policies.forteapps.io/auth-oidc-client-id: "myapp"
policies.forteapps.io/auth-oidc-callback-path: "/auth/callback" policies.forteapps.io/auth-oidc-callback-path: "/auth/callback"
policies.forteapps.io/auth-oidc-scopes: "openid,profile,email" policies.forteapps.io/auth-oidc-scopes: "openid,profile,email"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000" policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar" policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory"
policies.forteapps.io/auth-image-version: "latest" policies.forteapps.io/auth-image-version: "latest"
``` ```
@@ -1231,7 +885,7 @@ policies.forteapps.io/auth-mcp-authority: "https://auth.example.com"
policies.forteapps.io/auth-mcp-scopes: "read,write" policies.forteapps.io/auth-mcp-scopes: "read,write"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000" policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
policies.forteapps.io/auth-log-level: "info" policies.forteapps.io/auth-log-level: "info"
policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar" policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory"
policies.forteapps.io/auth-image-version: "latest" policies.forteapps.io/auth-image-version: "latest"
``` ```
@@ -1240,7 +894,7 @@ policies.forteapps.io/auth-image-version: "latest"
**Token Mode**: **Token Mode**:
```yaml ```yaml
name: authn name: authn
image: ghcr.io/fortedigital/auth-sidecar:latest image: ghcr.io/snothub/stunning-memory:latest
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: auth name: auth
@@ -1275,7 +929,7 @@ securityContext:
**OIDC Mode**: **OIDC Mode**:
```yaml ```yaml
name: authn name: authn
image: ghcr.io/fortedigital/auth-sidecar:latest image: ghcr.io/snothub/stunning-memory:latest
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: auth name: auth
@@ -1322,7 +976,7 @@ securityContext:
**MCP Mode**: **MCP Mode**:
```yaml ```yaml
name: authn name: authn
image: ghcr.io/fortedigital/auth-sidecar:latest image: ghcr.io/snothub/stunning-memory:latest
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: auth name: auth
@@ -1531,19 +1185,6 @@ GET /api/v1/query_range?query={promql}&start={time}&end={time}&step={duration}
GET /api/v1/label/__name__/values GET /api/v1/label/__name__/values
``` ```
### Tempo API
```
# Search traces
GET /api/search?q={traceql}
# Get trace by ID
GET /api/traces/{traceID}
# Service tag values
GET /api/v2/search/tag/resource.service.name/values
```
### Loki API ### Loki API
``` ```
@@ -1675,11 +1316,7 @@ team: platform
| **Prometheus** | 2.47.0+ | Latest | | **Prometheus** | 2.47.0+ | Latest |
| **Grafana** | 10.0.0+ | Latest | | **Grafana** | 10.0.0+ | Latest |
| **Loki** | 2.9.0+ | Latest | | **Loki** | 2.9.0+ | Latest |
| **Tempo** | 2.6.0+ | 1.24.4 |
| **Fluent-Bit** | 2.1.0+ | Latest | | **Fluent-Bit** | 2.1.0+ | Latest |
| **Gitea** | 1.25.4 | 12.5.0 |
| **Gitea Act Runner** | Latest | Latest |
| **Renovate** | v43.113.0 | 46.109.0 |
| **PostgreSQL** | 16-alpine | N/A | | **PostgreSQL** | 16-alpine | N/A |
| **Trivy** | Latest | Latest | | **Trivy** | Latest | Latest |
@@ -1691,6 +1328,6 @@ team: platform
--- ---
**Last Updated**: 2026-04-16 **Last Updated**: 2026-03-16
**Maintained By**: Platform Team **Maintained By**: Platform Team
**Version**: 1.0.0 **Version**: 1.0.0

View File

@@ -48,10 +48,10 @@ spec:
resources: resources:
requests: requests:
cpu: 50m cpu: 50m
memory: 128Mi memory: 64Mi
limits: limits:
cpu: 100m cpu: 100m
memory: 256Mi memory: 128Mi
# Service account # Service account
serviceAccount: serviceAccount:

View File

@@ -15,11 +15,9 @@ spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
path: cluster-resources path: cluster-resources
directory:
exclude: 'network'
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc

View File

@@ -16,9 +16,9 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
path: apps/overlays/upc-dev path: apps/overlays/eu
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: apps namespace: apps

View File

@@ -23,7 +23,7 @@ spec:
valueFiles: valueFiles:
- $values/infra/values/base/fluent-bit-values.yaml - $values/infra/values/base/fluent-bit-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -1,48 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gitea-actions
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: gitea-actions
app.kubernetes.io/part-of: platform
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://dl.gitea.com/charts
chart: actions
targetRevision: "0.0.5"
helm:
releaseName: gitea-actions
valueFiles:
- $values/infra/values/base/gitea-actions-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: gitea
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates

View File

@@ -1,52 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gitea
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
labels:
app.kubernetes.io/name: gitea
app.kubernetes.io/part-of: platform
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://dl.gitea.com/charts
chart: gitea
targetRevision: "12.5.0"
helm:
releaseName: gitea
valueFiles:
- $values/infra/values/base/gitea-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: gitea
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates
- group: v1
kind: Secret
jsonPointers:
- /data/postgres-password

View File

@@ -1,34 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: grafana-dashboards
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: grafana-dashboards
app.kubernetes.io/part-of: monitoring
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/dashboards
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

@@ -22,9 +22,9 @@ spec:
releaseName: grafana releaseName: grafana
valueFiles: valueFiles:
- $values/infra/values/base/grafana-values.yaml - $values/infra/values/base/grafana-values.yaml
- $values/infra/values/upc-dev/grafana-values.yaml - $values/infra/values/eu/grafana-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -22,9 +22,9 @@ spec:
releaseName: keycloak releaseName: keycloak
valueFiles: valueFiles:
- $values/infra/values/base/keycloak-values.yaml - $values/infra/values/base/keycloak-values.yaml
- $values/infra/values/upc-dev/keycloak-values.yaml - $values/infra/values/eu/keycloak-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values
@@ -41,9 +41,3 @@ spec:
- CreateNamespace=true - CreateNamespace=true
- Validate=true - Validate=true
- ServerSideApply=true - ServerSideApply=true
ignoreDifferences:
- group: batch
kind: CronJob
jsonPointers:
- /spec/jobTemplate/spec/template/spec/containers/0/args

View File

@@ -15,9 +15,3 @@ resources:
- cluster-resources-application.yaml - cluster-resources-application.yaml
- kyverno-policies.yaml - kyverno-policies.yaml
- secrets.yaml - secrets.yaml
- gitea.yaml
- gitea-actions.yaml
- renovate.yaml
- tempo.yaml
- grafana-dashboards.yaml
- network-policies-application.yaml

View File

@@ -15,7 +15,7 @@ spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
path: cluster-resources/policies path: cluster-resources/policies

View File

@@ -39,10 +39,6 @@ spec:
targetRevision: v3.7.0 # Update to latest stable version targetRevision: v3.7.0 # Update to latest stable version
helm: helm:
releaseName: kyverno releaseName: kyverno
valuesObject:
grafana:
enabled: true
namespace: monitoring
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc

View File

@@ -23,7 +23,7 @@ spec:
valueFiles: valueFiles:
- $values/infra/values/base/loki-values.yaml - $values/infra/values/base/loki-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -1,33 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: network-policies
namespace: argocd
labels:
app.kubernetes.io/name: network-policies
app.kubernetes.io/part-of: platform
app.kubernetes.io/managed-by: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: cluster-resources/network
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- Validate=true
- ServerSideApply=true

View File

@@ -23,7 +23,7 @@ spec:
valueFiles: valueFiles:
- $values/infra/values/base/prometheus-values.yaml - $values/infra/values/base/prometheus-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -1,42 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: renovate
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: renovate
app.kubernetes.io/part-of: platform
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ghcr.io/renovatebot/charts
chart: renovate
targetRevision: "46.109.0"
helm:
releaseName: renovate
valueFiles:
- $values/infra/values/base/renovate-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: renovate
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

@@ -17,8 +17,8 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
path: secrets/upc-dev path: secrets/eu
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: secrets namespace: secrets

View File

@@ -1,42 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: tempo
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
labels:
app.kubernetes.io/name: tempo
app.kubernetes.io/part-of: monitoring
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://grafana.github.io/helm-charts
chart: tempo
targetRevision: "1.24.4"
helm:
releaseName: tempo
valueFiles:
- $values/infra/values/base/tempo-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

@@ -29,7 +29,7 @@ spec:
releaseName: traefik releaseName: traefik
valueFiles: valueFiles:
- $values/infra/values/base/traefik-values.yaml - $values/infra/values/base/traefik-values.yaml
- $values/infra/values/upc-dev/traefik-values.yaml - $values/infra/values/eu/traefik-values.yaml
- repoURL: git@github.com:fortedigital/sturdy-adventure.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD

View File

@@ -1,148 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"title": "Log Volume",
"type": "timeseries",
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 0
},
"datasource": {
"type": "loki",
"uid": "loki"
},
"targets": [
{
"expr": "sum(count_over_time({namespace=\"dot-ai\"} [1m])) by (pod)",
"legendFormat": "{{pod}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "bars",
"fillOpacity": 50,
"stacking": {
"mode": "normal"
}
}
},
"overrides": []
}
},
{
"title": "Logs by Pod",
"type": "logs",
"gridPos": {
"h": 16,
"w": 24,
"x": 0,
"y": 6
},
"datasource": {
"type": "loki",
"uid": "loki"
},
"targets": [
{
"expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | line_format `{{.pod}} |{{or .message .msg .log}}`",
"refId": "A"
}
],
"options": {
"showTime": true,
"showLabels": false,
"showCommonLabels": false,
"wrapLogMessage": true,
"prettifyLogMessage": false,
"enableLogDetails": true,
"sortOrder": "Descending",
"dedupStrategy": "none",
"displayedFields": [
"pod",
"level"
]
}
},
{
"title": "Errors & Warnings",
"type": "logs",
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 22
},
"datasource": {
"type": "loki",
"uid": "loki"
},
"targets": [
{
"expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | level=~`error|warn|warning|fatal|panic` | line_format `{{.pod}} |{{or .message .msg .log}}`",
"refId": "A"
}
],
"options": {
"showTime": true,
"showLabels": false,
"showCommonLabels": false,
"wrapLogMessage": true,
"prettifyLogMessage": false,
"enableLogDetails": true,
"sortOrder": "Descending",
"dedupStrategy": "none",
"displayedFields": [
"pod",
"level"
]
}
}
],
"schemaVersion": 39,
"tags": [
"dot-ai",
"logs",
"loki"
],
"templating": {
"list": [
{
"name": "pod",
"type": "query",
"datasource": {
"type": "loki",
"uid": "loki"
},
"query": {
"label": "pod",
"stream": "{namespace=\"dot-ai\"}",
"type": 1
},
"includeAll": true,
"multi": true,
"current": {
"selected": true,
"text": "All",
"value": "$__all"
}
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"title": "dot-ai Logs",
"uid": "dot-ai-logs"
}

View File

@@ -1,25 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: monitoring
generatorOptions:
disableNameSuffixHash: true
labels:
grafana_dashboard: "1"
configMapGenerator:
- name: grafana-dashboard-trivy
files:
- trivy.json
- name: grafana-dashboard-traefik-loki
files:
- traefik-loki.json
- name: grafana-dashboard-dot-ai-logs
files:
- dot-ai-logs.json
- name: grafana-dashboard-opencost
files:
- opencost.json
- name: grafana-dashboard-pod-security
files:
- pod-security.json

File diff suppressed because it is too large Load Diff

View File

@@ -1,399 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"links": [],
"panels": [
{
"title": "Enforced Denials",
"description": "Pods rejected by Pod Security Standards (enforce mode)",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 0, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 1, "color": "red" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Audit Violations",
"description": "Pods that violate audit-level policy (allowed but logged)",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 6, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"audit\"}[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 1, "color": "orange" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Warnings",
"description": "Pods that triggered warn-level policy (allowed with warning)",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 12, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"warn\"}[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 1, "color": "yellow" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Total Evaluations",
"description": "All pod security evaluations across all modes",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 18, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "blue" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Violation Rate by Mode",
"description": "Rate of policy violations over time, grouped by enforcement mode",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[5m]))",
"legendFormat": "enforce (denied)",
"refId": "A"
},
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"audit\"}[5m]))",
"legendFormat": "audit",
"refId": "B"
},
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"warn\"}[5m]))",
"legendFormat": "warn",
"refId": "C"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 15,
"pointSize": 5,
"showPoints": "auto"
},
"unit": "ops"
},
"overrides": [
{
"matcher": { "id": "byName", "options": "enforce (denied)" },
"properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "audit" },
"properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "warn" },
"properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }]
}
]
}
},
{
"title": "Violations by Policy Level",
"description": "Violation rate grouped by the PSS level that was violated",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\"}[5m])) by (policy_level)",
"legendFormat": "{{ policy_level }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 15,
"pointSize": 5,
"showPoints": "auto"
},
"unit": "ops"
},
"overrides": [
{
"matcher": { "id": "byName", "options": "restricted" },
"properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "baseline" },
"properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "privileged" },
"properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
}
]
}
},
{
"title": "Enforced Denials by Namespace",
"description": "Pods blocked per namespace (enforce mode only)",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[5m])) by (resource_namespace)",
"legendFormat": "{{ resource_namespace }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "bars",
"lineWidth": 1,
"fillOpacity": 80,
"stacking": { "mode": "normal" }
},
"unit": "ops"
},
"overrides": []
}
},
{
"title": "Audit + Warn Violations by Namespace",
"description": "Non-enforced violations per namespace — candidates for tightening",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=~\"audit|warn\"}[5m])) by (resource_namespace)",
"legendFormat": "{{ resource_namespace }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "bars",
"lineWidth": 1,
"fillOpacity": 80,
"stacking": { "mode": "normal" }
},
"unit": "ops"
},
"overrides": []
}
},
{
"title": "Violations Breakdown",
"description": "Detailed breakdown of all policy violations",
"type": "table",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 10, "w": 24, "x": 0, "y": 21 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\"}[$__range])) by (resource_namespace, policy_level, mode, request_operation) > 0",
"format": "table",
"instant": true,
"refId": "A"
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": { "Time": true },
"renameByName": {
"resource_namespace": "Namespace",
"policy_level": "Policy Level",
"mode": "Mode",
"request_operation": "Operation",
"Value": "Violations"
},
"indexByName": {
"resource_namespace": 0,
"policy_level": 1,
"mode": 2,
"request_operation": 3,
"Value": 4
}
}
},
{
"id": "sortBy",
"options": {
"fields": {},
"sort": [
{ "field": "Violations", "desc": true }
]
}
}
],
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": { "id": "byName", "options": "Mode" },
"properties": [
{
"id": "mappings",
"value": [
{ "type": "value", "options": { "enforce": { "text": "Enforce", "color": "red" }, "audit": { "text": "Audit", "color": "orange" }, "warn": { "text": "Warn", "color": "yellow" } } }
]
}
]
},
{
"matcher": { "id": "byName", "options": "Violations" },
"properties": [
{
"id": "custom.cellOptions",
"value": { "type": "color-background", "mode": "gradient" }
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "transparent" },
{ "value": 1, "color": "orange" },
{ "value": 100, "color": "red" }
]
}
}
]
}
]
}
},
{
"title": "Exemptions",
"description": "Pods exempted from policy evaluation",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 31 },
"targets": [
{
"expr": "sum(rate(pod_security_exemptions_total[5m])) by (request_namespace)",
"legendFormat": "{{ request_namespace }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 10
},
"unit": "ops"
},
"overrides": []
}
}
],
"schemaVersion": 39,
"tags": [
"security",
"pod-security",
"pss",
"compliance"
],
"templating": {
"list": []
},
"time": {
"from": "now-24h",
"to": "now"
},
"title": "Pod Security Violations",
"uid": "pod-security-violations"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
# No patches needed — base already has "eu" paths
# EU is the default/base cluster

View File

@@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
# No patches needed — base already has "upc-dev" paths
# upc-dev is the default/base cluster

View File

@@ -4,47 +4,47 @@ resources:
- ../../base - ../../base
patches: patches:
# Traefik: swap upc-dev → upc-prod in valueFiles # Traefik: swap eu → us in valueFiles
- target: - target:
kind: Application kind: Application
name: traefik name: traefik
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/traefik-values.yaml value: $values/infra/values/us/traefik-values.yaml
# Keycloak: swap upc-dev → upc-prod # Keycloak: swap eu → us
- target: - target:
kind: Application kind: Application
name: keycloak name: keycloak
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/keycloak-values.yaml value: $values/infra/values/us/keycloak-values.yaml
# Grafana: swap upc-dev → upc-prod # Grafana: swap eu → us
- target: - target:
kind: Application kind: Application
name: grafana name: grafana
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/grafana-values.yaml value: $values/infra/values/us/grafana-values.yaml
# Secrets: change path to upc-prod # Secrets: change path to us
- target: - target:
kind: Application kind: Application
name: secrets name: secrets
patch: | patch: |
- op: replace - op: replace
path: /spec/source/path path: /spec/source/path
value: secrets/upc-prod value: secrets/us
# Enterprise-apps: point to upc-prod overlay # Enterprise-apps: point to us overlay
- target: - target:
kind: Application kind: Application
name: enterprise-apps name: enterprise-apps
patch: | patch: |
- op: replace - op: replace
path: /spec/source/path path: /spec/source/path
value: apps/overlays/upc-prod value: apps/overlays/us

View File

@@ -6,6 +6,10 @@ configs:
application.resourceTrackingMethod: annotation application.resourceTrackingMethod: annotation
timeout.reconciliation: 60s timeout.reconciliation: 60s
admin.enabled: "true" admin.enabled: "true"
repositories: |
- type: git
url: https://github.com/snothub
name: github-repo
params: params:
"server.insecure": true "server.insecure": true
server: server:
@@ -28,7 +32,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}" "payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}"
} }
template.app-sync-succeeded: | template.app-sync-succeeded: |
webhook: webhook:
@@ -36,7 +40,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷 Image: {{ . }}{{ end }}" "payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷 Image: {{ . }}{{ end }}"
} }
template.app-sync-failed: | template.app-sync-failed: |
webhook: webhook:
@@ -44,7 +48,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}\n⚠ Message: {{ .app.status.operationState.message }}" "payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}\n⚠ Message: {{ .app.status.operationState.message }}"
} }
template.app-degraded: | template.app-degraded: |
webhook: webhook:
@@ -52,7 +56,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷 Image: {{ . }}{{ end }}" "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n💬 Message: {{ .app.status.health.message }}"
} }
# Define notification triggers # Define notification triggers
@@ -61,7 +65,7 @@ notifications:
- when: app.status.operationState.phase in ['Running'] - when: app.status.operationState.phase in ['Running']
send: [app-syncing] send: [app-syncing]
trigger.on-sync-succeeded: | trigger.on-sync-succeeded: |
- when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy' - when: app.status.operationState.phase in ['Succeeded']
send: [app-sync-succeeded] send: [app-sync-succeeded]
trigger.on-sync-failed: | trigger.on-sync-failed: |
- when: app.status.operationState.phase in ['Failed'] - when: app.status.operationState.phase in ['Failed']

View File

@@ -48,8 +48,7 @@ config:
Match kube.* Match kube.*
Host loki-gateway.monitoring.svc.cluster.local Host loki-gateway.monitoring.svc.cluster.local
Port 80 Port 80
Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name'], stream=$stream Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name']
Auto_Kubernetes_Labels Off
Line_Format json Line_Format json
[OUTPUT] [OUTPUT]

View File

@@ -1,36 +0,0 @@
## Gitea Act Runner - Helm values
## Chart: actions v0.0.5 (https://dl.gitea.com/charts)
enabled: true
giteaRootURL: https://git.forteapps.net
existingSecret: gitea-runner-token
existingSecretKey: token
statefulset:
replicas: 3
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
actRunner:
config: |
log:
level: info
cache:
enabled: false
container:
require_docker: true
docker_timeout: 300s
runner:
labels:
- "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
- "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
dind:
rootless: false

View File

@@ -1,181 +0,0 @@
# Gitea Helm Chart Values
# Host: git.forteapps.net
# Chart: gitea v12.5.0 (app v1.25.4)
# Repo: https://dl.gitea.com/charts
# -- Admin account (password from sealed secret)
gitea:
admin:
existingSecret: gitea-credentials
email: admin@forteapps.net
# -- Gitea app.ini configuration
config:
APP_NAME: "Forte Git"
server:
DOMAIN: git.forteapps.net
ROOT_URL: https://git.forteapps.net
SSH_DOMAIN: git.forteapps.net
SSH_PORT: 2222
LFS_START_SERVER: true
ENABLE_GITEA_PAGES: true
ENABLE_BASIC_AUTH_CHALLENGE: true
service:
DISABLE_REGISTRATION: false
DEFAULT_ALLOW_CREATE_ORGANIZATION: false
REQUIRE_SIGNIN_VIEW: false
ALLOW_ONLY_EXTERNAL_REGISTRATION: true
ENABLE_BASIC_AUTHENTICATION: true
ENABLE_PASSWORD_SIGNIN_FORM: false
ENABLE_NOTIFY_MAIL: true
openid:
ENABLE_OPENID_SIGNIN: false
ENABLE_OPENID_SIGNUP: false
oauth2:
ENABLED: true
ENABLE_AUTO_REGISTRATION: true
USERNAME: email
session:
PROVIDER: db
cache:
ADAPTER: memory
database:
DB_TYPE: postgres
metrics:
ENABLED: true
repository:
DEFAULT_BRANCH: main
DEFAULT_PRIVATE: last
actions:
ENABLED: true
packages:
ENABLED: true
indexer:
ISSUE_INDEXER_TYPE: bleve
REPO_INDEXER_ENABLED: true
mailer:
ENABLED: true
PROTOCOL: smtp+starttls
SMTP_ADDR: smtp.office365.com
SMTP_PORT: 587
FROM: "noreply@fortedigital.com"
admin:
DEFAULT_EMAIL_NOTIFICATIONS: enabled
# -- SMTP credentials injected from secret (USER and PASSWD)
additionalConfigFromEnvs:
- name: GITEA__mailer__USER
valueFrom:
secretKeyRef:
name: gitea-smtp-secret
key: username
- name: GITEA__mailer__PASSWD
valueFrom:
secretKeyRef:
name: gitea-smtp-secret
key: password
# -- OIDC authentication via Forte
oauth:
- name: "Forte"
provider: "openidConnect"
existingSecret: gitea-oidc-credentials
key: gitea
autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration"
scopes: "openid email profile organization"
groupClaimName: "groups"
adminGroup: ""
restrictedGroup: ""
# -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed)
metrics:
enabled: true
serviceMonitor:
enabled: false
# -- Ingress via Traefik with Let's Encrypt TLS
ingress:
enabled: true
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: git.forteapps.net
paths:
- path: /
pathType: Prefix
tls:
- secretName: gitea-tls
hosts:
- git.forteapps.net
# -- Git repository storage
persistence:
enabled: true
size: 10Gi
accessModes:
- ReadWriteOnce
storageClass: upcloud-block-storage-maxiops
# -- Recreate strategy to avoid Multi-Attach errors with RWO volumes
strategy:
type: Recreate
# -- Pod resources
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# -- Embedded PostgreSQL (Bitnami subchart)
# Password auto-generated by the subchart; Gitea chart auto-wires the connection.
postgresql:
enabled: true
auth:
username: gitea
database: gitea
primary:
persistence:
enabled: true
size: 8Gi
storageClass: upcloud-block-storage-maxiops
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# -- Disable PostgreSQL HA (using single-instance postgresql above)
postgresql-ha:
enabled: false
# -- Disable Redis cluster (use in-memory cache instead)
redis-cluster:
enabled: false
# -- Disable test pod
test:
enabled: false
# -- SSH service (ClusterIP, exposed externally via Traefik TCP IngressRoute on port 2222)
service:
ssh:
type: ClusterIP
port: 22

File diff suppressed because it is too large Load Diff

View File

@@ -19,18 +19,12 @@ ingress:
annotations: annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
metrics:
enabled: true
prometheusRule:
namespace: monitoring
enabled: true
resources: resources:
requests: requests:
cpu: 250m cpu: 250m
memory: 512Mi memory: 512Mi
limits: limits:
cpu: 500m cpu: "1"
memory: 1Gi memory: 1Gi
postgresql: postgresql:
@@ -62,395 +56,5 @@ keycloakConfigCli:
"registrationAllowed": false, "registrationAllowed": false,
"loginWithEmailAllowed": true, "loginWithEmailAllowed": true,
"resetPasswordAllowed": true, "resetPasswordAllowed": true,
"rememberMe": true, "rememberMe": true
"clients": [
{
"clientId": "gitea",
"name": "Gitea",
"enabled": true,
"protocol": "openid-connect",
"clientAuthenticatorType": "client-secret",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"publicClient": false,
"redirectUris": ["https://git.forteapps.net/*"],
"webOrigins": ["https://git.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"attributes": {
"k8s.secret.sync": "true",
"k8s.secret.namespace": "gitea",
"k8s.secret.name": "gitea-oidc-credentials",
"k8s.secret.client-id-key": "key",
"k8s.secret.client-secret-key": "secret"
},
"protocolMappers": [
{
"name": "email_verified",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"config": {
"claim.name": "email_verified",
"claim.value": "true",
"jsonType.label": "boolean",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
]
}
]
} }
extraDeploy:
# -- ServiceAccount for the client registrar CronJob
- apiVersion: v1
kind: ServiceAccount
metadata:
name: keycloak-client-registrar
namespace: keycloak
# -- ClusterRole granting access to secrets and namespaces
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: keycloak-client-registrar
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
# -- ClusterRoleBinding for the registrar ServiceAccount
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: keycloak-client-registrar
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: keycloak-client-registrar
subjects:
- kind: ServiceAccount
name: keycloak-client-registrar
namespace: keycloak
# -- CronJob: registers Keycloak clients and syncs secrets
- apiVersion: batch/v1
kind: CronJob
metadata:
name: keycloak-client-registrar
namespace: keycloak
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 3
template:
spec:
serviceAccountName: keycloak-client-registrar
restartPolicy: Never
containers:
- name: registrar
image: alpine:3.20
command: ["/bin/sh", "-c"]
args:
- |
set -e
apk add --no-cache curl jq > /dev/null 2>&1
KEYCLOAK_URL="http://keycloak:80"
REALM="forte"
K8S_API="https://kubernetes.default.svc"
SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
CENTRAL_NS="secrets"
# --- Authenticate to Keycloak Admin API ---
ADMIN_USER="admin"
ADMIN_PASS=$(cat /secrets/admin-password)
echo "Authenticating to Keycloak..."
TOKEN=$(curl -sf -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-d "client_id=admin-cli" \
-d "username=${ADMIN_USER}" \
-d "password=${ADMIN_PASS}" \
-d "grant_type=password" | jq -r '.access_token')
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "ERROR: Failed to authenticate to Keycloak"
exit 1
fi
# --- Helper functions ---
# Upsert a K8s Secret: try PUT (update), fall back to POST (create)
upsert_secret() {
local ns="$1" name="$2" manifest="$3"
local code
code=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
-H "Content-Type: application/json" \
-X PUT -d "$manifest" \
"${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}")
if [ "$code" = "200" ]; then
echo " Updated secret '${ns}/${name}'"
elif [ "$code" = "404" ]; then
code=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
-H "Content-Type: application/json" \
-X POST -d "$manifest" \
"${K8S_API}/api/v1/namespaces/${ns}/secrets")
if [ "$code" = "201" ]; then
echo " Created secret '${ns}/${name}'"
else
echo " ERROR: Failed to create secret '${ns}/${name}' (HTTP ${code})"
return 1
fi
else
echo " ERROR: Failed to update secret '${ns}/${name}' (HTTP ${code})"
return 1
fi
}
# Build a credential Secret JSON manifest
build_credential_secret() {
local ns="$1" name="$2" id_key="$3" secret_key="$4" b64_id="$5" b64_secret="$6"
cat <<MANIFEST
{
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": "${name}",
"namespace": "${ns}",
"labels": {
"app.kubernetes.io/managed-by": "keycloak-client-registrar"
}
},
"type": "Opaque",
"data": {
"${id_key}": "${b64_id}",
"${secret_key}": "${b64_secret}"
}
}
MANIFEST
}
# Sync credentials to target + central namespace
sync_credentials() {
local client_id="$1" client_uuid="$2" target_ns="$3" target_name="$4" id_key="$5" secret_key="$6"
# Get the client secret from Keycloak
local secret_value
secret_value=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${client_uuid}/client-secret" \
| jq -r '.value')
if [ -z "$secret_value" ] || [ "$secret_value" = "null" ]; then
echo " WARNING: No secret found for client '${client_id}', skipping"
return 0
fi
local b64_id b64_secret
b64_id=$(printf '%s' "$client_id" | base64 | tr -d '\n')
b64_secret=$(printf '%s' "$secret_value" | base64 | tr -d '\n')
# Write to target namespace (if it exists)
local ns_status
ns_status=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
"${K8S_API}/api/v1/namespaces/${target_ns}")
if [ "$ns_status" = "200" ]; then
local manifest
manifest=$(build_credential_secret "$target_ns" "$target_name" "$id_key" "$secret_key" "$b64_id" "$b64_secret")
upsert_secret "$target_ns" "$target_name" "$manifest" || return 1
else
echo " WARNING: Namespace '${target_ns}' does not exist, skipping target"
fi
# Always write a central copy to the secrets namespace
local central_manifest
central_manifest=$(build_credential_secret "$CENTRAL_NS" "$target_name" "$id_key" "$secret_key" "$b64_id" "$b64_secret")
upsert_secret "$CENTRAL_NS" "$target_name" "$central_manifest" || return 1
}
# Annotate a K8s Secret with sync status
annotate_secret() {
local ns="$1" name="$2" key="$3" value="$4"
local patch
patch=$(printf '{"metadata":{"annotations":{"%s":"%s"}}}' "$key" "$value")
curl -sf -o /dev/null \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
-X PATCH -d "$patch" \
"${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}"
}
# =============================================
# LEGACY PATH — sync existing realm clients
# =============================================
echo "=== Legacy sync: clients with k8s.secret.sync=true ==="
CLIENTS=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients")
SYNC_CLIENTS=$(echo "$CLIENTS" | jq -c '[.[] | select(.attributes["k8s.secret.sync"] == "true")]')
COUNT=$(echo "$SYNC_CLIENTS" | jq 'length')
echo "Found ${COUNT} legacy client(s) with sync enabled"
echo "$SYNC_CLIENTS" | jq -c '.[]' | while read -r CLIENT; do
CLIENT_ID=$(echo "$CLIENT" | jq -r '.clientId')
CLIENT_UUID=$(echo "$CLIENT" | jq -r '.id')
TARGET_NS=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.namespace"]')
TARGET_NAME=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.name"]')
ID_KEY=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.client-id-key"] // "client-id"')
SECRET_KEY=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.client-secret-key"] // "client-secret"')
echo "Processing legacy client '${CLIENT_ID}' -> '${TARGET_NS}/${TARGET_NAME}' (keys: ${ID_KEY}, ${SECRET_KEY})"
sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$TARGET_NS" "$TARGET_NAME" "$ID_KEY" "$SECRET_KEY"
done
# =============================================
# NEW PATH — self-service config Secrets
# =============================================
echo ""
echo "=== Self-service: config Secrets with label keycloak.forteapps.net/client-config=true ==="
CONFIG_SECRETS=$(curl -sf \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
"${K8S_API}/api/v1/namespaces/keycloak/secrets?labelSelector=keycloak.forteapps.net/client-config=true")
CONFIG_COUNT=$(echo "$CONFIG_SECRETS" | jq '.items | length')
echo "Found ${CONFIG_COUNT} config Secret(s) to process"
echo "$CONFIG_SECRETS" | jq -c '.items[]' | while read -r CONFIG_SECRET; do
CONFIG_NAME=$(echo "$CONFIG_SECRET" | jq -r '.metadata.name')
SOURCE_NS=$(echo "$CONFIG_SECRET" | jq -r '.metadata.annotations["keycloak.forteapps.net/source-namespace"] // .metadata.labels["keycloak.forteapps.net/source-namespace"] // "unknown"')
# Decode client.json from the Secret data
CLIENT_JSON_B64=$(echo "$CONFIG_SECRET" | jq -r '.data["client.json"] // empty')
if [ -z "$CLIENT_JSON_B64" ]; then
echo "WARNING: Config Secret '${CONFIG_NAME}' missing client.json field, skipping"
continue
fi
CLIENT_JSON=$(printf '%s' "$CLIENT_JSON_B64" | base64 -d)
CLIENT_ID=$(echo "$CLIENT_JSON" | jq -r '.clientId')
echo "Processing self-service client '${CLIENT_ID}' from config '${CONFIG_NAME}'"
# Compute config hash for change detection
CONFIG_HASH=$(printf '%s' "$CLIENT_JSON" | sha256sum | cut -d' ' -f1)
EXISTING_HASH=$(echo "$CONFIG_SECRET" | jq -r '.metadata.annotations["keycloak.forteapps.net/config-hash"] // ""')
# Extract secret delivery config from client.json
CRED_NS=$(echo "$CLIENT_JSON" | jq -r '.secret.namespace // "'"${SOURCE_NS}"'"')
CRED_NAME=$(echo "$CLIENT_JSON" | jq -r '.secret.name // "'"${CLIENT_ID}"'-oidc-credentials"')
CRED_ID_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientId // "client-id"')
CRED_SECRET_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientSecret // "client-secret"')
# Check if credential Secret already exists in target namespace
CRED_EXISTS=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
"${K8S_API}/api/v1/namespaces/${CRED_NS}/secrets/${CRED_NAME}")
# Skip if hash matches and credential Secret exists
if [ "$CONFIG_HASH" = "$EXISTING_HASH" ] && [ "$CRED_EXISTS" = "200" ]; then
echo " No changes detected, skipping"
continue
fi
# Build Keycloak client representation (strip our secret delivery config)
KC_CLIENT=$(echo "$CLIENT_JSON" | jq '{
clientId: .clientId,
name: .name,
enabled: true,
protocol: "openid-connect",
clientAuthenticatorType: "client-secret",
standardFlowEnabled: true,
directAccessGrantsEnabled: false,
publicClient: false,
redirectUris: .redirectUris,
webOrigins: .webOrigins,
defaultClientScopes: .defaultClientScopes,
protocolMappers: (.protocolMappers // [])
}')
# Check if client already exists
EXISTING=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
| jq -r '.[0].id // empty')
if [ -n "$EXISTING" ]; then
echo " Updating existing Keycloak client (uuid: ${EXISTING})"
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-X PUT -d "$KC_CLIENT" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${EXISTING}")
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "200" ]; then
echo " ERROR: Failed to update client '${CLIENT_ID}' (HTTP ${HTTP_CODE})"
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "error"
continue
fi
CLIENT_UUID="$EXISTING"
else
echo " Creating new Keycloak client '${CLIENT_ID}'"
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-X POST -d "$KC_CLIENT" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients")
if [ "$HTTP_CODE" != "201" ]; then
echo " ERROR: Failed to create client '${CLIENT_ID}' (HTTP ${HTTP_CODE})"
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "error"
continue
fi
# Fetch the newly created client's UUID
CLIENT_UUID=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
| jq -r '.[0].id')
fi
# Sync credentials to target namespace
sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$CRED_NS" "$CRED_NAME" "$CRED_ID_KEY" "$CRED_SECRET_KEY"
# Annotate config Secret with hash and sync status
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/config-hash" "$CONFIG_HASH"
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "synced"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/last-sync" "$TIMESTAMP"
echo " Synced successfully"
done
echo ""
echo "Client registrar run complete"
volumeMounts:
- name: keycloak-credentials
mountPath: /secrets
readOnly: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumes:
- name: keycloak-credentials
secret:
secretName: keycloak-credentials
items:
- key: admin-password
path: admin-password

View File

@@ -20,22 +20,20 @@ loki:
limits_config: limits_config:
reject_old_samples: true reject_old_samples: true
reject_old_samples_max_age: 168h reject_old_samples_max_age: 168h
ingestion_rate_mb: 15 ingestion_rate_mb: 10
ingestion_burst_size_mb: 30 ingestion_burst_size_mb: 20
max_line_size: 512KB max_line_size: 512KB
chunksCache: chunksCache:
enabled: false enabled: false
resultsCache:
enabled: false
singleBinary: singleBinary:
replicas: 1 replicas: 1
resources: resources:
requests: requests:
cpu: 50m
memory: 256Mi
limits:
cpu: 100m cpu: 100m
memory: 1Gi memory: 512Mi
limits:
cpu: 200m
memory: 2Gi
read: read:
replicas: 0 replicas: 0
backend: backend:

View File

@@ -1,30 +0,0 @@
opencost:
exporter:
defaultClusterId: launchpad
extraEnv:
EMIT_KSM_V1_METRICS: "false"
EMIT_KSM_V1_METRICS_ONLY: "true"
prometheus:
internal:
enabled: true
serviceName: prometheus-server
namespaceName: monitoring
port: 80
customPricing:
enabled: true
provider: custom
costModel:
description: "UpCloud 4-node cluster pricing"
CPU: "5.86"
RAM: "1.46"
GPU: "0"
storage: "0.34"
zoneNetworkEgress: "0"
regionNetworkEgress: "0"
internetNetworkEgress: "0"
ui:
enabled: false
service:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9003"

View File

@@ -6,15 +6,13 @@ server:
resources: resources:
requests: requests:
cpu: 150m cpu: 250m
memory: 512Mi memory: 512Mi
limits: limits:
cpu: 300m cpu: 500m
memory: 1Gi memory: 1Gi
enableLifecycle: true enableLifecycle: true
extraFlags:
- web.enable-remote-write-receiver
extraScrapeConfigs: | extraScrapeConfigs: |
- job_name: kyverno - job_name: kyverno
@@ -58,24 +56,5 @@ extraScrapeConfigs: |
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance] - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance]
target_label: instance target_label: instance
- job_name: traefik
scrape_interval: 15s
metrics_path: /metrics
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- traefik-system
relabel_configs:
- source_labels: [__meta_kubernetes_endpoint_port_name]
regex: metrics
action: keep
- source_labels: [__meta_kubernetes_service_name]
target_label: service
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
alertmanager: alertmanager:
enabled: false enabled: false

View File

@@ -1,45 +0,0 @@
cronjob:
schedule: "@daily"
concurrencyPolicy: Forbid
renovate:
config: |
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"platform": "gitea",
"endpoint": "https://git.forteapps.net",
"autodiscover": true,
"gitAuthor": "Renovate Bot <renovate@forteapps.net>",
"packageRules": [
{
"matchRepositories": ["**/10x"],
"assignees": ["edvard.unsvag"],
"reviewers": ["edvard.unsvag"]
},
{
"matchRepositories": ["**/auth-sidecar"],
"assignees": ["danijel.simeunovic"],
"reviewers": ["danijel.simeunovic"]
},
{
"matchRepositories": ["**/forte-helm"],
"assignees": ["danijel.simeunovic"],
"reviewers": ["danijel.simeunovic"]
}
]
}
envFrom:
- secretRef:
name: renovate-env
env:
LOG_LEVEL: info
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 4Gi

View File

@@ -1,34 +0,0 @@
tempo:
metricsGenerator:
enabled: true
remoteWriteUrl: "http://prometheus-server.monitoring.svc.cluster.local/api/v1/write"
overrides:
defaults:
metrics_generator:
processors:
- service-graphs
- span-metrics
storage:
trace:
backend: local
local:
path: /var/tempo/traces
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
persistence:
enabled: true
size: 10Gi
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
cpu: 100m
memory: 512Mi

View File

@@ -1,43 +0,0 @@
site_name: K8s Launchpad
site_description: Documentation for the GitOps-managed Kubernetes cluster
repo_url: https://git.forteapps.net/Forte/launchpad
repo_name: Forte/launchpad
theme:
name: material
palette:
- scheme: default
primary: indigo
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: indigo
toggle:
icon: material/brightness-4
name: Switch to light mode
features:
- navigation.instant
- navigation.sections
- navigation.top
- search.highlight
- content.code.copy
nav:
- Home: README.md
- GitOps Architecture: GITOPS-ARCHITECTURE.md
- Developer Guide: DEVELOPER-GUIDE.md
- Operations Runbook: OPERATIONS-RUNBOOK.md
- Technical Reference: REFERENCE.md
markdown_extensions:
- tables
- toc:
permalink: true
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- admonition
- pymdownx.details

View File

@@ -1,91 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Gitea backup helper — interacts with the S3 bucket via a temporary pod
# Uses the gitea-backup-s3 secret in the gitea namespace
#
# Usage:
# ./scripts/gitea-backup.sh list # list all backups
# ./scripts/gitea-backup.sh download <filename> # download a backup to current dir
# ./scripts/gitea-backup.sh download latest # download the most recent backup
NAMESPACE="gitea"
SECRET="gitea-backup-s3"
IMAGE="minio/mc:latest"
POD_NAME="gitea-backup-helper"
ALIAS_CMD='mc alias set upcloud ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
cleanup() {
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
}
mc_run() {
cleanup
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
--image="$IMAGE" \
--overrides="{
\"spec\":{\"containers\":[{
\"name\":\"$POD_NAME\",
\"image\":\"$IMAGE\",
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
\"command\":[\"sh\",\"-c\",\"${ALIAS_CMD}; $1\"],
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
}]}
}" > /dev/null 2>&1
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
kubectl -n "$NAMESPACE" logs "$POD_NAME"
cleanup
}
case "${1:-help}" in
list)
echo "Listing backups..."
mc_run 'mc ls upcloud/${S3_BUCKET}/'
;;
download)
FILE="${2:?Usage: $0 download <filename|latest>}"
if [ "$FILE" = "latest" ]; then
echo "Finding latest backup..."
FILE=$(mc_run 'mc ls upcloud/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
if [ -z "$FILE" ]; then
echo "No backups found."
exit 1
fi
echo "Latest: $FILE"
fi
echo "Downloading $FILE..."
cleanup
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
--image="$IMAGE" \
--overrides="{
\"spec\":{\"containers\":[{
\"name\":\"$POD_NAME\",
\"image\":\"$IMAGE\",
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
}]}
}" > /dev/null 2>&1
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
echo "Saving to ./$FILE ..."
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat upcloud/\${S3_BUCKET}/$FILE" > "./$FILE"
cleanup
echo "Downloaded: ./$FILE"
;;
*)
echo "Gitea backup helper"
echo ""
echo "Usage:"
echo " $0 list List all backups in S3"
echo " $0 download <filename> Download a specific backup"
echo " $0 download latest Download the most recent backup"
;;
esac

View File

@@ -1,165 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Gitea restore helper — restores a gitea-dump zip into a running Gitea deployment
#
# Prerequisites:
# - Gitea deployed on the target cluster (Helm chart via ArgoCD)
# - kubectl context pointing to the target cluster
# - A gitea-dump zip file (from gitea-backup.sh download or CronJob)
#
# Usage:
# ./scripts/gitea-restore.sh <gitea-dump-file.zip>
#
# What it does:
# 1. Scales Gitea down to 0
# 2. Restores the PostgreSQL database from gitea-db.sql
# 3. Restores Git repositories to the data PVC
# 4. Scales Gitea back up
NAMESPACE="gitea"
DEPLOYMENT="gitea"
PG_STATEFULSET="gitea-postgresql"
PVC="gitea-shared-storage"
HELPER_POD="gitea-restore-helper"
HELPER_IMAGE="alpine:3.20"
PG_USER="gitea"
PG_DB="gitea"
DUMP_FILE="${1:?Usage: $0 <gitea-dump-file.zip>}"
if [ ! -f "$DUMP_FILE" ]; then
echo "Error: file not found: $DUMP_FILE"
exit 1
fi
echo "=== Gitea Restore ==="
echo "Dump file: $DUMP_FILE"
echo ""
# --- Safety prompt ---
read -r -p "This will OVERWRITE the Gitea database and repositories on the current cluster. Continue? [y/N] " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 0
fi
cleanup() {
kubectl -n "$NAMESPACE" delete pod "$HELPER_POD" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
}
trap cleanup EXIT
# --- Step 1: Extract dump locally ---
RESTORE_DIR=$(mktemp -d)
echo ""
echo "[1/5] Extracting dump..."
unzip -q "$DUMP_FILE" -d "$RESTORE_DIR"
# Detect SQL dump file (gitea-db.sql or gitea-dump-*.sql)
SQL_FILE=$(find "$RESTORE_DIR" -maxdepth 1 -name '*.sql' | head -1)
if [ -z "$SQL_FILE" ]; then
echo "Error: no .sql file found in dump."
rm -rf "$RESTORE_DIR"
exit 1
fi
# Detect repos — either a "repos" folder or gitea-repo.zip
if [ -d "$RESTORE_DIR/repos" ]; then
REPOS_SOURCE="dir"
REPOS_PATH="$RESTORE_DIR/repos"
elif [ -f "$RESTORE_DIR/gitea-repo.zip" ]; then
REPOS_SOURCE="zip"
REPOS_PATH="$RESTORE_DIR/gitea-repo.zip"
else
echo "Error: no repos/ directory or gitea-repo.zip found in dump."
rm -rf "$RESTORE_DIR"
exit 1
fi
echo " Found: $(basename "$SQL_FILE") ($(du -h "$SQL_FILE" | cut -f1))"
echo " Found: repos ($REPOS_SOURCE)"
[ -d "$RESTORE_DIR/data" ] && echo " Found: data/"
[ -f "$RESTORE_DIR/app.ini" ] && echo " Found: app.ini (managed by Helm, skipping)"
# --- Step 2: Scale down Gitea ---
echo ""
echo "[2/5] Scaling down Gitea..."
kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=0
kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=60s 2>/dev/null || true
echo " Waiting for pods to terminate..."
kubectl -n "$NAMESPACE" wait --for=delete pod -l app.kubernetes.io/name=gitea --timeout=120s 2>/dev/null || true
echo " Gitea is down."
# --- Step 3: Restore database ---
echo ""
echo "[3/5] Restoring database..."
# Drop and recreate to ensure clean state
kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \
psql -U "$PG_USER" -d postgres -c "DROP DATABASE IF EXISTS ${PG_DB};" 2>/dev/null
kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \
psql -U "$PG_USER" -d postgres -c "CREATE DATABASE ${PG_DB} OWNER ${PG_USER};" 2>/dev/null
kubectl -n "$NAMESPACE" exec -i "${PG_STATEFULSET}-0" -- \
psql -U "$PG_USER" -d "$PG_DB" < "$SQL_FILE" > /dev/null
echo " Database restored."
# --- Step 4: Restore repositories ---
echo ""
echo "[4/5] Restoring repositories..."
# Need a helper pod on the same node as the PVC (RWO)
cleanup
kubectl -n "$NAMESPACE" run "$HELPER_POD" --restart=Never \
--image="$HELPER_IMAGE" \
--overrides="{
\"spec\":{
\"containers\":[{
\"name\":\"$HELPER_POD\",
\"image\":\"$HELPER_IMAGE\",
\"command\":[\"sleep\",\"3600\"],
\"volumeMounts\":[{\"name\":\"data\",\"mountPath\":\"/data\"}]
}],
\"volumes\":[{
\"name\":\"data\",
\"persistentVolumeClaim\":{\"claimName\":\"$PVC\"}
}]
}
}" > /dev/null 2>&1
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$HELPER_POD" --timeout=120s > /dev/null 2>&1
# Clear existing repos
kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- sh -c "rm -rf /data/gitea/repositories/*" 2>/dev/null || true
# Upload repos — tar via stdin since kubectl cp needs tar in the container
echo " Uploading repositories..."
if [ "$REPOS_SOURCE" = "dir" ]; then
# repos/ directory — tar and stream into the PVC
tar -cf - -C "$REPOS_PATH" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/repositories/"
else
# gitea-repo.zip — stream and extract
cat "$REPOS_PATH" | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "cat > /tmp/gitea-repo.zip && unzip -q -o /tmp/gitea-repo.zip -d /data/gitea/repositories/ && rm /tmp/gitea-repo.zip"
fi
# Restore data/ directory (avatars, attachments, LFS, etc.) if present
if [ -d "$RESTORE_DIR/data" ]; then
echo " Uploading data (avatars, attachments, LFS)..."
tar -cf - -C "$RESTORE_DIR/data" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/"
fi
# Fix ownership
kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- chown -R 1000:1000 /data/gitea/
echo " Repositories restored."
# --- Step 5: Scale back up ---
echo ""
echo "[5/5] Scaling Gitea back up..."
kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=1
kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=120s
# Cleanup temp dir
rm -rf "$RESTORE_DIR"
echo ""
echo "=== Restore complete ==="
echo "Gitea should be back online with restored data."
echo "Verify at your Gitea URL that repos and users are present."

View File

@@ -1,20 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: forte-helm-repo
namespace: argocd
spec:
encryptedData:
project: AgBBeKBtro27DMJ+9YFjSlvv+XWyotBojRbaDVPDfwH0MoXST9zUKZSXDgbuJ5sy8dQYhfCZgtfjcNpWbCmLoURZMm1aPa0U05bG7Hz2EnO+39qcgpH/jzEzp0AsZ9YgMafgI0EyL63RnVmvvcFT81fNe4P6vlKjz+dyhIuXbELhDjabNE+QwsLmQkty7R6isJSx42+aEGliD69FcFjlvwz75L7y2Rck1eodUVbaTX3qdG/bSFCTWawCqnj27oEn+qYwcvrS/B8MsiG8XkcmAxy3QVHAW4waBQ/6IALqBaviXunye7TOujgfn996sQWEZz9e6tVsbh1I4i/1T44rtqiot3EuFMMuYN1C6JMb53lUI9KM/TMWM3U/B/MgFR7DbXRF8/VRkBqVJFv/PZWKxTNbnYEhdPBPZojZNhHntdFZNw7l9Pr251mPI9UCU+Mc+RKtluKb+c40C9Ty1TGaV0DQ3UtA+1zbotDVGx21P7tAwplz22czu0GSUZu3nDDEPoyQoI1YG4TBvqKwuFbxJZASUglDmwyzFG15QIn03zE661ETR1ETXMbqxJR8yWmY7G5R1aNMCX9oc5l+xS6lUuUsI/fmN58VA3i82j4jEB/+oPyQ6bxsiteGXkvIz4ONxh2Ve+FmgxaDNLJNH0RHnJ7J/mJARfn43o264ONq7eTt2HuOgoP9UIsOPXuDmlBo1oZ0zxWXUEST
sshPrivateKey: AgBNgdWFu4x9CEgKftriZCnjSPNgV+RpAxi8/N0XawXATbQEtsMHfctTsRtwsOvaEvCehlLq7dJWRmPiuTGZ7fA2vaetoj7ppa59kHJtEO4teVcU5n64glLV5EqZbwm/nel0b5ri+otrY2zjo/tomt/7e0K30OZbe3Wghr6pDH8MrScAxO+VqgJHqqXeFayY2cC4e/mZXGu7J+FKBl3si/i6nKtKFhaqCKF+Wn4tou7wZDIdx4A+jWUxTnQquQU8IdaZof0hUNKrmmD1ayK5mJxfOBLqt9TXoTKGwkZJkdr0Pvo8tx1DqTCfpyL9Ur7B7H4G5x9n+AaD6+Iwn0eohty+NPLoy3v6JYYayZb9Foi41EzRHm+W9Cc0TcDBNTN8kR9u713n+YNuFHCaALx4iR28svfKHHjsOEzZ5688WXuph2+eLm+DW03obRDGJYi25j9URYX31LJSTMEs0rhhsUn3eeipUjQ2Fdo3xXFnALLOci0XjRd6YvuR5DN1pkj4KFcMzHvTkyM8jG9eTHGloW55zFTNtb9QkNz+x75RRpCmNlT0bKqAjxfov70K8gbSIzZ7d77iai68JQ/FnTjuL8Gx2Jyr/ltlVnql4fpv1u691SCCTvoz6aAVknn63SXdrnJ9hv+/Nb01MZhYAciYgvAAoxAOT3UOpIBnJ5h/VUacJ+Op26jtSsWrMjAtVSN2RjqzXAbVe/HONpE76vEfDHKizHEUriT7y1WoOUxcbwGIFAzSDM2OidUW/SstCNCmGQ9Whdq427X+eQIh2h+EpCPYXy3YTuWfSISgkA74bSVeX+2mHpwgo+5GPfiAQmHajdZLYTo+GAaty7VdNWTL1AEdgFkYR4TwaeR8DcLe03efg5Q1SLd1I0+kjtkVQcWsnTnchnMcXK1tvOAcNc9MY9pKNh/gW/nAnsAyfEA9opl2LFyywpeDizDdowO99meiJB7+ISS1lOI5f7xvcM6ovEcTugj4Dwv05546Zxb1ziIGbhq5kiD5FIMCPzBo0vX4XXeUa0GRIVCHRxgygi17bTMwKGKRKGusduBVbc/+OKaX5ciAtxPSq85Z6XCXSEXzajsIFkasQtm7tt44U80fAlwmgAfGdRQMF5CcEW7x5WyXVb6mZUt3GgQmPdfrP6kTgaxXGTkGsbb5bcsbf3DeoAdiJ1rJPBVX4z10cJsdRCKI+d0VwVLu59eheOajdvPxdhR+tNhJfavaUKtj7THrMJUq7WPZq4GlPQpO7S3+Q6V1e6eaSw==
type: AgBf+Wb64fdsnUEwpxRcWg8fRtMi8MisZOA9gm/uwA2u+OZUNh/vHO5hzjxG3NVeT+6sOM3iJJhnLHTv7nxykP//3qG/PqkVPPYPvGm5v8sd698Ga146FsOKQzga7mn8vGZ2FYyZmfq2l3ZBS2j+12M6TtXw5a4dqCtrVq5FbvBr9MBKTxpHdKnpgddxyWYxE9uSgFm+2IrT9hH7OAhke0NrQQwbDFpOI/y2xhgaIGp/E78G+8Ijwc0ofrd8XIjKWcesXD4uQqTBHZfcoanMWlSqrfXDxSRiBNVkHot1AnE8DiukL3+Dntxr6bh3bIZWvsHuNa9fQnjH6BNskYQSYopreHdujsvYDm19vqHNpkW1mTmjdimWgxX5Ot2kzcnuguA4Qn2JjpLicT2GbNIEcI6rjghTM6CATOFWmiF933uIXhDN9RlfMR/+hTLisFvnZmdsH/6byvoe39UxavtVEKsSPCa3h2FN5O2HKrccWrAZ9fQdDtIzwVMXi48rsi5sB05By2WetsUMu3JdRlM8noQ3qtrKzdtjyQIUFJcIxRkL8mKz5lkXPKjn7qlItUY5VG6dnhHQRTxYdAxn3jKMpYh0jEtloI3m3SEXhfE9JPbA4RIgNi8evnRBMKwBwEr8mU52DbB1J3gU7Gm9IOYBsOgq0U1BkzqCqIaThoUWXCNGkAG3rc7uLuyOaHHVEBLsmu+m1cE=
url: AgB3C9i/Ue7oHmeoLQCr8BVYIqYIT8EmrhkFjj+vjBSTynYwoFonaiPMqDJ2XewJV9WpO6wL0IYH/cK0BkhbPhjN6oQ88Qn1MkuVCjtGht1cUeOzRtSE8hdqoOKaagFb4pcgwwm8ldU7bry3KioXnz36YaU7wcddUkHVQYQOp6DvcxtjonsLUFHecDcW+qL5aVzwI3CBkn0xMrc2kRiO2q2ZjR7BNNddNUw5f1v3fYNE0PARd+bVyegSEMuBRoh1amautWEfuYoCENPqhfd9Umbs9eml/j86pj9mvkVZ2CzRt78V+C3ciMZM6QlLV+BdiPm99/igyvZScDEB/tUrMaj/K6qhMYG7r8jNBQrsg3luvTVFcC75e8aSgfRJ0QDiYmtZ5fP+8AS+l2pyRFMkbOMAk62LCqAiXuXk/vC412Lrk4NkEIxqCDnf771PTOkbXYXNlHzC2w1AZSkvPqKjIvJbLCkCxGFVBeUmlZzcCIYDeXTpIRwZsVIo+sGpjbR8soeTb8suw6GqYjgXPhXm/S1kpL6yAXfCFCXmyxntMZCuWuFPPWfwzn5GDfSJS7Hz43cuh7vwrQ78xC1wQRs7EObWNUbLIfvWEypvncYmixKY8+02SrDwzcwADimJKMxs7Bp10rkMO4HkpcFDisVyHCgBbfG3BYFo0sT5+FN6b5Bhuo6hUaCzEe9HAeSIFNuKaX5wYbY1MzsRoWOvydStoQa9KwVeZwBPzZV0VW2c3wMRT1pNN4s2THGlphw=
template:
metadata:
creationTimestamp: null
labels:
argocd.argoproj.io/secret-type: repository
name: forte-helm-repo
namespace: argocd

View File

@@ -1,18 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai
spec:
encryptedData:
anthropic-api-key: AgAdnPQzl2SNSqyAZkoBdEEGEjXfpMJUNMg4e040vxri4jbjoTnmwQ/DjmmIGtoInjhQA3NZRUKcIuDrO7nGMXFOPY9tSfhpdaAMWTji1CVZvmdJnrQwZY1LFBwgbozq3RSZZQQcZd2EPrhhsU5UlmacKk2WpuQdq76twfdU5WVD8xalxbt0H5UDvihuQa3sYX/rUIDZK2Th5IfaFqYZk5JvaxR8PFwBh9IOooGewWXWrsxVT510lxHRotSDIgKt/jl8ymsVA7IpddnbQHr6K77it7BxR6fLvDGaTQeQW0RtPZwsLvB268OTwp338RQ2sjmVuUDErWViLuk1V6UFmj5vhRR0X2dy/EipkWEJwR0Zf8JCW+vAXJ4NFiVFjmOsiTXoLHCYp0k01EwFcPQpnswKSdaQ3tuCEa4Q+rHu2DiXyfSuZ1MnWnTyLSx9H51VYUgIPwQa+BUVoVnAswypw4yLQHrfLu9TSvsNl/OonP0dSmgSGaByTc1RzwpvHBUAaOYII2LuuvkF90oA4cChGkGe1KuYKHFqiBdaT9jrsfeBUpgqFAH0uH9DNjBjU5EjlUtV3bdO7wXDHct9EOBZniKMH7cIbXfT2TBaormeHNqFtXxSRXy1szDpXiDD1QDoAJx0ylrwwqix7XPBlqSt91OZ8N1VPSUPvfnTQf7zirVWJNckU9fW6tsnQ+GDOOEwERPEAhqxWuw6V0HMab4VzpbGgWuvtQY5CsI2bFUYWSZAIJ6H8E5ip3lb8ilWtYadhx8QZD3aKi7k5h4bFWd6M0rg0uj7bfbW8YXauTS6Uz4b9FlvgrAPkmcWG84um/jsmP6a0YhvJDGKHMHoUmY=
auth-token: AgBq/MM/+fqi1H6L5o/xFeKYMfcn6+bmbkepbq97O0Ob+dFKWuyfXmzqM2Dha+NKpXcu47qSBbLpaOdLS5ONq7JDIcZ2byW0ae9khBuL7k2mCa5ZPh96LS46c+v1qNJb6F4NQe+jWSx2H9LIShoihom+lRueBT1/uthW3hnUUUgQMgXU3NYimDNAg6JK6VgX3yKkI7ePLPAet7+ykaL1aPXwcfCAldcobPmls0vxMQDtgd+o4nLqx2sKArFplnwZ9G1SEz2dRXNFm8HI4HhgtOBdv1ISjXO8XGRZWnFqgX3s3BqcwFcPqVuFGHo+2ZvAUW+mdkuAEJOloJEoZXJkFCP+l7/yMV7/FmFAhntqfdTwijEJ+rP/sUbUNSzn6BKM2ITRGXEJkOeNq4xfpgS16AOs+O0DWkIAr0kWCJG+mkn94U7ALCGYQXtKLy0g/87MJ3IwaUU7gowVawHoDmpY0QoAYDJwpbIa9yMnlPOzJANkc99dl85sbLeQMvkkb4RqQlbMc80gkFFhbU5/0kKX483ylqhRi0b4ZjGHgl72s/8+FhxTDkSXJQ2HbrNPGlna+B7TAgxYZ9IS1cpw4wSNzSCk23Rt3STHnXuu/QXRLeMijXGBDFNJgU0JNKLp63JC1gftHcNhqRL6lxK1tLyo5+0Cnh/KJN64b1DpPXZnzdaeR25GQFSDxsymjLm2BFATtufRPJqeIT7tn5TRGTr97fH6LQv/LuJ4VnjS6WLsQoFdUhDuNug2LDuyLHjpKg==
openai-api-key: AgCQR+HezEHwiwFD1pIkla1Byzz91SBoUyaWVTTuBrBDq201KvlLmqahjQwpKWs4YvqhmKkbF/mXKuMSyFut72IwH025tfmzzmTryOD6bOQDxI0Ws5NndnztzKv2qw2Js0c+6lAOJR+lEuo1ynGlG3hfS+bGShShvLZIDcD/hDa8IfhQBh7fKyFslJ52KxKK+zeEFDVQcx6Lrq/zP9IPH2QXz0bVQYlRyrQ4vqONO9h2pjxNAhDxuJ5FPk+Hb4ClGjyRPHFT34ZYsYdvHUgkjL3nOeaOUrc0GGJOKYZChfsK/JrqX8iOqgbE+0vQvMX+q8Tr6Ynr0KRKMcGPr6ak3eCNknJaZ1EABtkyUS2u5TzKqMzYq5DF4kpvPApIvaQW+VS05zD8piTQSZEFNRwwXDpyru/R4MmPvwt7OdSM0SJpQ8kR7tsuRgBPFY2Eduk6fhrDmMVgHze3AoRaqyh87CeqR3Z7/XofbdgZBqEp2vwiY425ArB6yMLpVvO6eB+yoTgCJ/UpfOxBDHE1M6U3goJhk/YeOy9UpTfRD1VyA2OuzJliDNtC784wxnGAsOk3qKk1bjjBqUzwITK41mOWqNLKCn4Ol9lXZtbbqLIiU4lXv4Pl93mVSuPLOIXINLGMRFl0rlvDICEjrrfE+JmwKx6i45sqsk3/hz1Fb+/bKFo7pl9JnAXvr5WfDW/rflwBrokfJVeYsSm6R2yxGWlosrKZIwIRctOut1EbbylurI5pIPp6vHguyIvZDDxwj3Qk3ewU/WVAcI9989aLR++UrZ5FGXt3apMk8BY9bq7DIQOUtgfPO98+cVPdoZSIh5bhQpCHAVPMGcGTdIPkuQbxSai0FsP8ipdHx0fluI6p/4v1Rv/BqZH0X8dnwPQoqUhSD+C575w511Fc8PEzgaG63S9hHx5Azw==
ui-auth-token: AgBgKRwErv5MtWIdqSN5BSQ/deHFE4469gzqL6gwp/3JidtBEiAAyuOIbc9dRCWuJNcdtixKOZT1rKuI8O/e6oJ6ABGAx+ckDfH468VKsR4AJhdZGcxub5j79NKccbNA5MKTnqxlU05zukUOkgw3wdsOyuQr3RnfL31GFbEVyDMej+Xr7Rs0fWrfqpasCi45/PEi8fkofMg45kGc+LMfDgovNtkKla2MAXwYlUk24SA+lgCFoG2DHB50GdTK2byCks4Px4J3jQqqHdLUfC+/gyqimPoVUsVGb5SY0xeNepKfTz7lHEkmDOB9R1wfv7pUaLMapc+eVrVN7reLHKOA47IEedDpnlLm/fyEfPe7ktkejc59tPyIyxBubrN+Vb/+6bDgi7xsXy/N5uD/2sPqWVgI2D7veWOJEjM+GYOYeb2iRbf7KlrokCXD/gRMiQBKqYyUdjWx40MjrhxzPArOa+cL0kb1CUgwuyjmqq5hNIUO2wo6/kkeNtUeT7r067LE5rtMoZG2EAfw/xrL1CI2F+3c72JzRd//aClt1MsSfPPPoFz0TlWSSc7qNDhYHuQf//G/Zd29Nk/Ac52HVwdkl2AXJE/GIFdwi+ypqK5mjmSUTn/JVE0ZaA3OpHOF/01meISuqBo+HuAWCV8sw6en0F8AF+DF8rw8t5hDdexScXep5QuJmm4CYgqThyjz2V5xME9oMmGA+Yiu/MJ4AlkLowijpXkKEWpEOBRpRRhu2q2szhZqmx1oKJw+qJ40CQ==
template:
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai

View File

@@ -0,0 +1,17 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak
spec:
encryptedData:
admin-password: AgCbGhJduOlTEmA9OLys+ZmuXLU5T1dmdm+fRlrNnPtvjpDCmfKYnOP4rbvBs+Kvf1Y1TZ8fbR2UTISg96pXfY/TsSoJlU024NpBWSZ4e7HxJgHtq0ZwaNi+ruw41JiQ01LrGJ23q34WaPVFV/VfCDaWMlcIJ4gfRb9+lb8jXB6/sr9zSVNWtfrPoYdECcXvr3sOIz9Y5D1eiV/uuDuDQqm+vKpZp8a3bvWO5sB/uQ6a+ECCI/NUFJHtEP8sGLsCGoJMrO1UotXCvKLpIflcxMwW3NlzHd2e7vEPRdelpc3tlT88wd33lQ2LuXMJpQuMI6eHIcVA72UtTqD5bePU1HVWb6d8Dlg6UdebUDIphuJ5rsV3uKYNMgOxn+mvBVXhu5xRBsyanwHoUIKcvvuXaN93YtXHk4khgmt6GNNFdLFEZ69wDB5tPLrlpepvKzNzFYwEIRngYNQlBT3rn8677XgBhUltoq4vYoEqYredVCrh5N7R+DrYD2OYmfsB4WWoMrmE1+Kn/HA/Nu6i5M3mAYXg3NP6Yv9dI5ynMQKVBtwGnFn/hDym1cixmxdQs2FQ3ZaD62zzcXQ0Sc2CuiYVtKg8MZjLCGQoV6ZG7UrnuZ0cyzKE2SGmOaWwFHkRysXoRZiQ/eBybUWwU4DXigczSJ3zBEg57pjEsvloHqAEcGjMxqV1VqXpty/6tdFKkRvCgO6pH5wIQA==
password: AgBeVrYU5CDY8yBA0huXHEJuxykggruULsQB/Q/2sKOZTGZ9KyF1WSB2qJXG9CPIuwBz1YR/hlg4ezF7dQRaWNJspLXeTQgAmjwvgsa3XrfQEEoSFbt9KuN2T28W1S32YbQQJOQHMiMjn8+6YtIWTVM/14MWMGIw8J54Js1cWMsOS4BDT7qe059YMbivxOuPi+J0duL28doowmy5A393804LjyBTzWynB1XqmeBd9V5dJWeQSRl7jpvORR+LKGm/JXF9dX97kyeews0o89qa7VnPqxb9ZGUXhSufMEC8qmm2tXgyj80HaP+PE2aZ0M69o7Xmn1FkEaIxQ8stTyepG4+c7iySbdeqIWPWf0LpgPlkdEuQ7hXAtyNd2TLnD0AxNBiN8bKl2DGLIxaOpoa2PvVHqU08fFQuyHGB3uCvAhgO69Pvb6q62Og2hQHgNM4WWAgu1h9K/F3R4DyzxC0JPYAjfw+XIZCiuYX/UYpjEFXIw4pFU/2SCDGbbPNMKFVxppuBU8h8m7/QTawkJZ0y9ivv0SuIG1z2ExjX73dWk+h1my732wyaoGV+os4R5io5O+aznyvFUsH23N+jNj6WzMSromdYxHKIdmBakd7FiNqsXFy0vm7+aY+tie9WnB/ysC9zN13UpHTCkG5mn2E0X+ctzL9RM7ylQMMesfmk5VCnzewEQloGtEby334CCc/HH1X8+Ws9AO6okEMGplVI53cI2rXwXncFis37qO9yMeV5EZ18wfjXeaKxouiv/zZRuY4wnaojqJKIh0wnbwcOK1f8
postgres-password: AgBlBR9F4xBm5B31+dwptBjxH4LvjOBd/kDr5AJ2CRpjBZOsEqZa2jnFjUujlNFKfRDWDdzt3FQ6E7VW8Qt2hsrngDgRcIH6aPoZBOSZ0xVMpqLiY+Ek8YlZXH86Oh3+m4wUFNtcM9lZwPGU09/byC5W5zWhpmsypd+waXHF6VJZmweayupvbCwHOL2af58y3HXdl7uqX2/VyTR6mVZKcLVePO/QHNTMBlkPlfw+HKjDuluPcS3MirZU42k6E5OGo3v9XheF4HoiiY5Z76YkBI9k1shYzQYk2w48iPg7QV4heZxUDgwcrkRdJA/eEAUQejakTM4m2yx84ef1xzW2vnnkxz/OTO4KCZtcM7LddBOuO6TS9LSb7JEcfx8SJ7+oQTVhEk0e7nEAMGQQrcOE8TOX122ejBnMkhJra57VnHQ73Xb6fEjQy9dhz6pEnukagIvKKsZ1gZwJkY6qBfq4MtE7nGRCL5OFdXjEFwjdHG1pyR57sYqwvmX5ufE0YZkymdxdmFl/Wigh8uIeZAkfwYgH+8bWg/1Z5yLYzXxhx4S+YjOfLO4ncy2zILw8LnIZvnrOCWgogiiYOJM32E78jZadmZOGVVXbgRvOPLZpw8458Frwsk9oaXMjL7TEryXsH8U8NzVeUBOnnmW9Z6w25a755Jkb2AwnLYbfxMoHhJm7DTTERNM4ZM4umPjUEsXhlTRo/rJ8oE4El54sdSEHIuau0U+ZDbfE0V0y2YhVK1gx2iKeDdvPmGp2LGRURT6AQNc/a5c3/S9MXLEgVSiaSUO5
template:
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak

View File

@@ -1,19 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-backup-s3
namespace: gitea
spec:
encryptedData:
AWS_ACCESS_KEY_ID: AgByl2/ljFIHzTD0Vy7srQoXRfdJRLG+WukgeLMJeiJm9MOFJBNEkr5ju2DemDNdRcViXQLN3yxqT/L0fG0rz+kaPQtLeVFToqr58vokxDasHw4WVIUOosi6wE+yaI16H6vxvdV8dck09nHE3fdBcwctlvjqsY7mKvyx4tYKdGRDoeJ/C7shYoDTl4E/ZtsSRkfOQ4Ojm6M6FU10zn03OOKrzaOUczxnqbAyGNFrZvCUGG38QQVnyYm/2HLofQgheSSQx8p0w5IgPRRhBM3IAyCLGkyEA8qHXSO3J5Y2m1izAoU9RVsHAVUMVTfYEdtUMADkDcywKzi0bi0ehBxDs2PuC89Uz4s6rKQ/v8xU+jUf3KogrZxsbuCh1iFVO/NCOLvQhLY5wU/946wl1WmS76/HxkM/D9Iq+KF0VsP0pJAA+SIyJQ3Bh0a9GnKRsJjCfO8qX0M0WSXhOpjw+4DvBoe653mV7n+LOEjQy7LJURFaz1HzQColelhhlQ5tHCN8J1jtjscFNiHZqyzBBm226X3oxr0cAC6e/l53ohNkKS3NA5aM/wouBrscs1/CfmDYKxujLyGqontFRQc3rtCZ29829u/RmuwietIVGeu+ooCacSM73zDqGYKM7HRr/Y7QYxuW0TiSaJhYMZQqsC4uo7ebZhRa2bWbCTHiVCs3jDdpSRyPgEECOvnOJbkTsh0e02HtrUEx7HBjLZua9FD2sskr8C9XJQ==
AWS_SECRET_ACCESS_KEY: AgCYxgmto8ytLz8QMm25/nIqqezlWWennhbPSPMB/aDYR+zW45LvAZbjwVp6wwSR/U5iXwfdZg5/k8+8CzGAKDjxc3Nwygih3cUpqVBOl+uOzD3W3oNDsyQckhmNA4jidwIbJF6ecV8O+GVuU19+E4QrkHTIP9lN5pnhkfIR7nMRVj4jdcNahH2O75huadGQII4GG+rmnGX1012IAhknq20CiOCbby3a2yHaU3om0srO1TkW/67jioQX2IvgUh7jVl6c4r1Y6b+glwV4bHc9GecDqNEF6uj6uy8ChNh1khRfUYVysIQRM9m1pV/qlKiUW/wjDZOjoW88IAg4wl2MMOFQby27jVwQWSe+kUPwRMf7HSNWoq/DaE/z71cMsdeEnAXtQMGwNzOr4EGM1n/faPGDWkj306l1xjoXNO2hLCNX8BspSBxQDWWADBGClC6C1AQX0HlsZLV0G18VCEkjTwvPRmPqigxzHganxWiWM0q3DfGrc+JvnGFW0r7waoKI5vIzxwzCbb3I042+3z2vsvo7ZW2mez+eKgeD0MvhRW2SlBMiE62MGJQL2BvTew1iU0Xean+19WGZO7PPysnOH6kU098kTJ5GjQpxlI2C+w6QC18q8eQeIvVyd/7wH+k7RMCDC+No6MCcYhDlcQNbIir6JJ7vIOd3n5NKXdg7Sy3SnjkyDPTOjXTwyn2hHkATMzUxgn/0frNZYSsEMTuoNlOfcZLr7UbFv+Qlr49rkAEMo3deohfGiQSD
S3_BUCKET: AgA6ulIpP/DrYOQ7iqo7CSeaSj0L/+PXDPZ7SxPmdu/wrqbXw7nlxAyp+7QHqkUub5XgVhwrk3KPmqUPcECPDbHdt93+nlM3PVD0yPNkVaijncEPRVccGu/VhE6Nae5lXI9U3pnAVAXn+7z8iwRpF/vr1qIGyQwiizsKyfBQhvRSupzOvY8sypbItDjyjttxlwMRorGI94GObeUS89kSx/MB7BWZJMtUuRRSG6YwgH/XIkIWbo2p2LD90EhNtaT9rYa1PcGP9BVcgHf/9zVCI4+1LWbfmZSgobwAEKLhzZfhzCM2DhN31CsVWhpp0x0gYmspNbtQQvoosKmeBPBT+BLkTabAhjx00rvVX3J48Er3PaVAjw6JxT1KSdaUuCmcIzX3O8ys/8PNacaEgEiqmeuIPgID8YHSXSfs9RIUkjKBWGydjE90lMQPgnqOBkPWTd1BNRqHj60D2pFp0/h7+j8/OfBj7doDp9ECwcdQqjwzX4pNi7WQiGd+Ri0/7DK1xSAOL0lwgg8VSrqCOIdasAZRVQuHhuWwKMyhdQyQCr+zCOQ/bLQaPeF1m7tKxFfU4lNz5tRiC8AOQI5aHX2gRrkpfugD3G9qFFQMl9EPCdNeBh/ezVWxSxekWvQTuGJ2WnLD33BhsZVLKjXa+tHjD/BsQjQgdCiqv8J9gPgtngx/pAFf0NaQezVU4tBfaYD4tetcrZDz5UtW5tTHaF4=
S3_ENDPOINT: AgBfKbxdU9hZrxIpbM6b+hDthXQ+uYrjWHGmxSdjGvxgHB2P7E+HqblPAHjIpiAsGiPESV75ZKs5/BdEBOoZpbvneAuRgVLZ5mxkWiQ35q7sJpWaUg47icnlEPFoFj8oxYbi1NRAYB5hbc3AU0s11mw7wre0pRZu0pgSidHk/lyuSXOHKQzuhXxKYmV61LjMxCQGwDNwbiDNuSZyU7AZ2r+vr2W7Tzu6G+tJctwQd3HOnYbOLMV4tBv93nc7EkU4tbdvdIvkGHEmKf4r4F+nGvKZ3fZie1QKyQvG/4+i8OKqby9XJtviEEfBqfrk5qb1dNQlqCfA4ThQ11MmRiP8VoaUp/yoUHHYACNY9HLBp+N5Cgbbcxo044U1c8b97I6ZOZJ2waZ9XkrBpYPPXWJRKxLeNgYoJqn3yMZV/U561DO1jLZ2cwQXXaFrm1WT7VjcB0czdJHW3FcOg9lzYKMCCTTX+cD4M1oK992931eECQxBecrtlQYD+NlJng8ARm7myTACOZGYMQo2gjdM4ZBh9KqoCT2jrFC6E29YwfRAIXrhiWdZZxOW6Bu9Txt8FgxnIlSz9iZ1hvbfdvrSZTilJbAAULKFqLUgNpQbdgYHtGXQkzFHqYmbdZ0vJ6taIli7y+/Rz6xKcql8uJLxnuncLvLvXHxXl+rWeKrAMn+jPvnuCcdq6yVPsI0Nz/B4EQRL7Nzl9XYQxSybAJACrrCjgEuHsquoPpuznlGuk2scuakXdWOzMg6i/MEk
template:
metadata:
creationTimestamp: null
name: gitea-backup-s3
namespace: gitea
type: Opaque

View File

@@ -1,18 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-credentials
namespace: gitea
spec:
encryptedData:
key: AgAN7QteEqfzTrlCRR4Hi50nPJ6GGktFqOJIMgYvAL+es3ELUy9KLGtcyaC+Bf+b9lmG5+UgiCdz8jWbnVI7xEhDKXx8+3KzLw3nPAhYPO3j4XSi3FhbHwE0p5IGlZ0/XdXP5SRr/X0P6wQewapLSClGoya/ErY549ORfL0KgbcUrPE4ReSKbER6R/biRzSpkngxljf0rw4uruhNTlmBvnL/0nRSdZGtww7fLhcQceARutGihKHjOTkzmv0d39hHJA+4xNTi4PCbfOUJBkKwriy7NTi6AZkGRH2b06m+/IGfYYvTp/2gdtf/omS0ZhIoEVeRLhupiwkRAlclOSk3EXRsCcMiH8Gp5Mp4lUuc6yYHwM9ONAbq+P2OlUTjNDOntgH4Z6p02D3pIUf6nterSTuga7haGr5Ph0OCYBk/V6KkswwuTsSBAv7EI5TXrZoALp0xxXBXS459sc21UCgp2K5Xdhb3K92KwtcQDND7qAdIVZXAgf1wahm2svjRWhvjnRVLqChbb8MvkEQC+queHvQvIW+/5mCEb/hAEY/nAHJqTqQhsHnpPxzMhLvxN+7S2zvqSC48zSVAbKvwNjJcaiZ3s4dTjgCaryT307Gg4FI9bHixtk30fB9ZUkMu1cm51QC8ld5CBVKLMi8mHSezshiZ1n2PVYMiOivbghDZQxJ+uutZVOlrF65uAtgnQgmm9q/fTD7FWQ==
password: AgBdnjk041bBX2rwz+q1G6QDk+QrHfvuzoTo1pCRE1w7NLACFgGwzeSTS7gjPXwtjIkgganpZYgT9ZwWK8+gcT6Sae/CBTcDPgE6Y7FtfpG244wgp+mn891TDQXONW/OBAYk+706YDbHfqqz5AQxKAEorJ74RZuLTAo6kB85A/eveEnQqq4cByJaInz0eprB1A4hGfBLF3XArjiVdL7f4X33mR4scH80GozJiqUnG6ruVv5jcEBXnzySVrnCaq4VgmgJTu5Lsjz+AyH7niGTGFIsVBeOnVyeodKwgDk4N3HeSj4w9No3ileZELD5nLZh+/OqdQHw2niBz5/hPjDyXw/p96PfLM3CFygXfs/Au1DqdDf8ccZufhF9OK9CCQqlIgCWK3W7lhsdhcgK65XisRG+hh37Vcxke+A9ZoLGMjIT43KejTqwE8r3wRxPrmejBUcVHohiD7L60dFbcUp64hSBktcIGwEAWn89gmD7Ar1gCoCUmFCBCTVD8c5LQ2W2xeeZ1OrtDpMkOustzXUkzqkqiCB6TPMqDQ4J5FXCyoTuHrBnieLWaKL82kdUohF1+Jak3UGe0rSpOxBmoNC/17yVXyiwBGOUZZP+DJyvwiJu1jc7j3USQ0HRCcR7af96IcwGxge4yWbR268oI53yiJlwh46JFcc8RkxQQaq3Nh3RoiGJohqx/or8JOIFsXEtUW4WSLNQa2tZ4dxRI8F83JnBCo2W+zvL2yXKRP/LLMXfF7nAOuXPrYKrRcdnnRhP1cdDUSIQk8c6gzvKJrsEkt/j
secret: AgAR/5Tj79Rk4fgvPwvRU63q2xF0fokEe0/KWQG9CGiYWwut8wB8PlST5WEcKWIjwNfGpSsyHRQHywIVwDT3m+3/wxfQ5eOtlHIfMuglSymqeLHvj9tuM2EWabd4o9frKHHJigNojXS42JZ1jVSW+PeEfNrGFBARWWaDYSA5586/M8U5qi6UOBIkdyG+796wqqIeUM52d+/lwHHcETQOdhwa5/B4IrbRB33CSv0UHmfM+MeNtUWEnza5+8tm+1mPFyBJ2N9isXxMeyHXWutwOJVsAjEe+X35mC1C03eIsDjpmpjQ+hweUhinu6zse7CsIwcBDuq4/+1xd8xaQ/lmYc01FophuAcVRqDZIhlaAjCLI7gCfX2fliluVuaUbZ/A4DY8Q/psOteODeI0kVEMCaL13T1j6tZZWyDqDAB91xKFh2ne4DOOTwkZLP2StMt7rAENy/eMld+VNrfNliHJUVJGMoPTzCxPaZQxJfRYIbpE5uGGA2fWkteuX5VJRqZ/8JshdBAUVG1XOSXlvM1zlW9KlyvO3j3KhekKG7bY+NndIwFMNhujG3FGWCA+k3MYQfAks3FWhW+ZXA0vYZpnPOJuYO5tqqDO4GLsrdJc5GaXMC7kcey90PqkwzkwhpxX7/X414w3bHWoYttmFCcQwSyA+S029gSFBIleNTKkjpXsNl8qB2NDJR1lf2kufO016WP07PB1NK0hGjvI0isz0uHZILrAD0i55t858/EjWsD2gg7jK2ww69vVxi2c2o6k370YVv92qoZCayTuYsXF0Ay6
username: AgAmO891YOV8ZtWTNjOF+ZA+6yG69Jdvozq49/XCQabuVXSqoESWA/V/6RPL8+up8dFa+c/u0J+s+Pu1cJL5zjZmJtXStOIuhoIGBTkWBPgHQ11hp/5apkR4nt0gWR3fWQtz1v/073WsaQGhEyKHB4ZsxAXbwEQ41f+TQzdkvxu2DT07Kc4O0jN+QsAl/YtPLk2YW2WaiY/Q7dDq5+LKAbKVRR4Aa03OuMVDapPGOEogu3wC8vl9lkYMhPBrIosq9ycqk4roenKbtmFSIkzytLOLRKSNEmPUSklDMrO8HEGSByASxJP10jgUZW9wY4lE/DmsjuS2CQsGLXhZAye2S3hgGyewnIA1ZfsnJTb7jSsnQ0BVxm6HL9lKy/1PqDzMuE0Ou+LHiIt5DLbCrDmn0F+n8jhNlpCbtiZXVL/Tvo7SAruBe858CYXc2StFHNi5hA1dRP2CCGklY2Wmi8u5JeOdi8RXdhw8deZtI0G+I1NltqzhXdDR55kMWahy2BTImJrGj8owrdg5bFz1Ne63P7jiS80BeUfDKffYAWuslDEiKkhO8YSoVj7ixeWIfXvZRyaecaCMcZ+VuNlZy3R5CbFTcIyZkfotK0+aA9S/q+iM6K36ghTxpQv6WSecnHpeR8mkVckCGVo+iiCPIFfQw2VwOHn+BtvbH8pfr0McKoGHAvoDGGH9sjgDbjAGwCYFYr7qHTKaFc1vbVcUNw==
template:
metadata:
creationTimestamp: null
name: gitea-credentials
namespace: gitea

View File

@@ -1,15 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-runner-token
namespace: gitea
spec:
encryptedData:
token: AgBZkU9AgXmFoBURd4WImEFFY4k3LTvGmMJ0ZEZETYzQNS9ICyM0TZfK9nF/bSY48y9t2Vd+VNNW2WJhqec05HE9rLe/vQmo2L8OlBLbCpFL9vCEFJ/h8AiWdHGsLJtCH6tOJwBQ667KPfAGFeYZzKf1XF5HA8O6G1+v7kO3mtSNSIYWq7S/swe5QO+bgtclkeDjg3xgOhjYTzlOsg+rcTknhwGa2YpL22rFN6Tbf94oTK6xVBYrkly1nhiALGYyGnROSv/Ua8+M4xEF5tr0QslwjRmzaQTC+Skt1HKXogsWVzE03RZHWQpmcz1ltaKfikl8WQwBmOBDeg9xNRfBu5n5jli9NvdKZgMEIyQQZtp27QCsIk+FLxbAb2Zy/hG3G93Jvk9mRzgryXyi0te8w+iEP1WtVOYHSyLmk0AMCahn2hPIfzQvlOXJxoSFhLbwm5eSwtqYTQkQ2WFKmwaznSZBUmk1a+LvxV5pOhPMvc52w0v8u13ng/YzDDnyjY/6WdYX5pJtYOGUFDt79yd2Bd0Hc4CH9y50dy84YfdlC/prgTVeVfoJFW48qhE4sNkBhL28PjgkcYmIOb9fskP1o3RuHIoR28y9ipZMkzdLimCRzIQxf4GmdOlGsoEEvujgiGr1ta8ilRoAjjDAD593PuYELFRfGYjk4KGNkz6MP3t4UDRlG8umXMQIqhEWUaRGytqMnfrPGiGPAGCBfAAes3mooDNO/NO+WwNq09fd1OWFeDvzJMOLxWLy
template:
metadata:
creationTimestamp: null
name: gitea-runner-token
namespace: gitea

View File

@@ -1,19 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-smtp-secret
namespace: gitea
spec:
encryptedData:
password: AgBMuISQeA41mtBIOo686sND2EO12Jv7BIogL5G7xxt7wKfIk88dQrU74vs+fv3OtPce2Ra63QkR6po31M9fZkoiYba5yqJtEOc6em3y0ZxM/UzavbRvHwrvsWEYqmHBnkCUjcJijdGbSX+rHyGsXLfZO0gOjqXO379Zl1fzmV4p3F5REXFQm6HorVtWX/LOSRj9GDW76l6KgPR/A+CbtAx8Cq0m3D3fyiaVLEReP3uOgBOo70APHj9Yp56EcgOtVa1pEgCxR4ctXeS4t/EliWcHc/JT4TBdRBRDYPKLfME6FvJxjLjaSZcWxtJrJzCv3+vA5LlfObuHY31aSDRqYwO4VBCPhf3Aa6Z5UXgUnmAtJRhHa9pKSSjW48jgNb1jDPIkQn5XgB2/twJ+gX3inAkrTQ82JJ75Rz7XWC8KmYkOtkgXgU2buCa4nIfPeXOr5qvutyywxV1Ge1nK0fQYneQZVFXlHTbAQXBJMpVvJoJ+G3xGjm1904/iBGkVKmNrQwaABUsGBC6ZIHGOTa45GBqrg3ODU2Gr61SCYxv6m3pMU1msR7QYne0oqLCVD8mLDaeSeiQI4ZY9u4ddsVwM6l2BFrT6+3IQuYPBgOoodzDVlCgmA7hoekhpak9vZ0loSHaWDXdNt75SemAjsQfwCO5sSEkr+wbCJEQpXh5p38RMZKTuOh3nYEGQEx/MQNl3VD4FarK/zOJM9EO9IkqdM4LnqVo3zPX4KAPosS1PPKS8
username: AgBF6MiaI1x2xQOUoF4NUh4MeFF64Db3vywcEO0FdJ0U9EirVFMsBSSiqJLy8ok43ha72s+/RLBNHiSSRKX1UMWwwCsfs+LQJNh9EetgHRxoyqkHiqRMX5V2acU2scdPE/FCFQFOYzAjweup+kP8xNu1WKuDtPBRiAgBNDfW59ihFi9TgOJQ2AnDHottjm5CNaWsbTOSgZrXqzCEChfHu5K0W9cty9ENHYqnYDfcm/zPLYeUPW0gVN5GJq3lPo9vZjM7T2JnwryjOkBaPCRCzHOpRF3bMrArFrjbUlH6gdI0APf4CzGLMMKol/jTMG2tLBseaNQHfbz6p1vFYExCL60gSN46fzh10zIWaIC2O+SgoLLOizkQZWf/v86cdRerBSl6PFmbRUO18XUQ4SyR/WPM71HD1jeLnUZjKtkOu+fqQKlv8kBSELHGqiURNYDnbmUA1LQpdNkDnMkRS+uzQ3XwWCSQBAn+u9wh69kg1oPVEN60Nc4KpNwFIg25aycGkP3cMklfl3/u9nr5KruwtJe+hl2ynSk8zeEFWWQrBki7+88CH9aWVW/GTA8Ho7Fz+gp4ZUdUA0WhH2LRAQIN945pvJIkHm/AYAhH6pZiXdBzYeguPY5VEf6hDVM1sa39aSZzs81cj0YHxbjR/BoBxbUAa9xW7JYH2rcIqXDhJB4zq2H++8e0eABsdQC3tMmE1eQA51d0yg8+2fX+CRkcvMCmI3VjS/mdirrtnctv
template:
metadata:
creationTimestamp: null
labels:
allowedToBeCloned: "true"
name: gitea-smtp-secret
namespace: gitea
type: Opaque

View File

@@ -1,16 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: auth-oidc
namespace: music-man
spec:
encryptedData:
client-secret: AgAKQ24qP43Dopfb6LfH0o7RIQjK3IMjII8knmdpfL5ldXJRGaYYYQDjZJcIE5HiLoEFXKkLHm39k83l6sJ1fn1RCbbskSGmosYNenJgxOqTZdlyC911km1PVMu890N2JEqUVRQb4YTn8+1dYUglWPL+zaAki3wJ84G5uM6ZY0R7mMEp/5J8RFEeByzdTiHLtB9U2fPnBJiGdKfFN/ysnoZOLN0nAHvvnTEa4pkO2BXNrrhVl7Nje/y9IDmQntCNIHomF9Fy4bo1SpChj1vRXWbLETlMdFkRINHMVWAvemoIDv+CaHVRaaoy/XtTngqA5lyZZwiwiix+HBIDPE5HAnGE/Zqgc2L+ByR3ak33H6O6iGJoVdR79uu9PiW0iaWJwruXgSfm7Fq0KuOHNg0QzfTqsZf0jgT5RhT4apSGHZOl2g4QUkMUee6BPuFM435ZZFdOfDZw1YRdqdFvI6OJgx7+G+kwwnEa125LuBgbJzqLRVxH0LbD9HoBoEqZUB4H0V+0PUY7uVSKcpZUi0RwiLjcoHJzaJlgJdaEc9GrDhh8OvDXOxsBJcRrJDmAH5WbGLXbq7+HUknfbJFkcl6rg9bPWDah0DDf98weqUdafpn0TTt7qoOGW1z27yVApGn9bm9B7ykCa/i9QtX/6G9RTaoslTU45pYzA1Clw9ccRNxv7O+2afU5KZORZPYNXCho4K7tpR7Hw788Rj68x1GYxrZh82+s0fs/SYpvzvN6EiFxKY8R+f1azefD
cookie-secret: AgAPJomNWVV2m8izVg8CQ78mnXP9qQaXh1WaIELv4JzwyUIcMOddDDq8PWzmAiWfV24iYj/oy9QB0jruI8+78H5nU6QsF5KB3DlZiDjzwaUeNOs1q4wPsQ/aU9JygYpWD2dYUn4pBYqQQxJyf/gJ1u6jbGMbjsO5J3kkK4RaFiTNt+A3151nmYVgWToknTPPcTaFgpH0xumhQHicO9MyaNsSKOJ1c2AeUiWHqc/0a39tuchCgq4Gm1FOKjXXfevcZw/IamnRiEegoAr5qgSPl7e1xfZeG49FJacsfpWEuQhmKGsaePTHok141VnkKHhlmFdewe3Sn+kSWJbtAx6jky0sqClUKqopVjxfj5IKwcucE0NjxxX2HXmQb/e9nY3BLQ1CRjQQVCNa+JJy6DvAbcERHs7t9T7ATXm4S5lawp9fp8UB1ySy++q4Qvmc+4/16tgugqVEths/n+gSYLWAM8Ibd42RhNXlWqjzvpAUXmHX3Dt/26wNy+5ZnRlcwOIotPBdDqQKlp3fGKc2loC2JHcyN1PH31EwY5+h4pdU/sGSU+EsPd43hALUHq3XzvibHVDQTzyerJgC77NX6kvu2W+V5iWN9LYAAVjMh5yzLYjfgoC/zzIiOIe9yRf6zJzW0nhSkdcoC8EH0lcwEEDp0d/CuO0A6AwWOBaer+tnQtjHeZse9RdKEfyisJDDa7xUNj4T9e8Z+shdkU94vladQpVP1lyRd4auXF7B+T0+1Pkmvac8wDHDk67iomi+1BpS+/2jdJUGJO+91+qIdCFLPi1Y
template:
metadata:
creationTimestamp: null
name: auth-oidc
namespace: music-man

View File

@@ -1,19 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: renovate-env
namespace: renovate
spec:
encryptedData:
RENOVATE_GITHUB_COM_TOKEN: AgBliRXcjCrWeZKa3A0RuQatvluoF30R8HEAt7aVJwSf9EbMtB/ozbtsbHxYTsyfpdnaewD+BMRbaDdQOTpmlPxfK5eqKFuLXJGCXmiiMEHvK3GG0y0STjfgC2GyrXvtmh/lg/Awod9ZneJ2+Xoi0X7ZIngWwHXmgvsRfczKtwZAZm/YVDLGHRW2BCwBgzStZquopxYGQRa58WSx9XkE/cV8UOb6Kw+4fGMKggJPr8ERUQyermCZ0HBOpkQwPpWoPhRFdCMj2qasVLttkK/jLG2romy3JZ59RGrQ7bj2p9IdJujqtteWb4vDdOc4jvplKWlB8QptZ872a2MPeyvopqkCsmnsfi6tup6uBaUzE8Hym0olpqK73dCjc4RP7x64KwsdGOyi2aJgEz1/Ro7GMu7sam5sdpTjpdA3EbyC8tZrnlC51mUlq4ghP5XELGj6DjvLWZ3dk0UOSQ4vQ/KLGyA29VBgvV3MdabpGNhs5VlU69p4RofxoFT9n9CrtJN8FPOnMUK6L58Zf3w+eYJQMuyttgf/h/stQUx6OU7R4d/4xA1j4YCFRxw2mXaYO9Tjsxsq2KB1v3TzyZKtZfYLeiNhv/rAfPs7krACs1ncNXVtS7sP3G14+Asvu6EdnlM86LThBmIeKRW8lSry5pO3IMOIzNGrqulFnuTGP9fy9cs5d58q/E4sUXoY0Liiod92+1RO8dpyn+xf9afCAei8lG6TFNPWdLzcl8WOJgZLWmRdRNhezOmSZHxnh8P/Ul2oxhrVZSYGLhvIfBVU9oY+s7lgw8FpM08RLdP3OsKigs5/uiE/56qHZHvj2YZf6mg=
RENOVATE_TOKEN: AgCV8DmDz1CT+wjtghW9ERy9tY/GCZfehPbd3vqikGx8tsZzxEoKukJzkeRJmsHqDTXylFMZHB+saYuk+vWOeMT7oGs/6wIC7eC08GN4OcBYpihrTSrzoAkp1WwLJV0KoNGbtbgoQXdlPJOWBjs6X90mrFmpwRewcaADks/uS/ONZw3MhiSbukYN3L45uL6b6gUp1Yl4nDyCyvCDoRpr1FQt2MMltyChimUtLXoMrnV3iO0oFlFMITJRjvauYrKXdD0xyWxji3Rbrowbgr/iFkz/0fTpg6AwhP/ISRWtO+NDB3kZ6/CvcK2a1phTIgf2eiHfvMQIwnpfRauyUPtNHkCInC7OOtn0eyHZmAqkSaJTN1rJN3X8lRIgjhtZr9upE6ZCLK5kMdq87oDjtvwc0X0AyeTxV3EGXh87Zfda97KhG5LSC3uShzc8AYPA7SGxMTuoF4wON0Qa7AdAeGvtL+2GuhRkHhmyOeY4iORrVo8TlJsN1DsZmrbRArJWzvMcb31gefxTmg8tSZhuLOo4RHsUayCNIPmW/AvpcbGJS1pPXS8nPkDqSoeL6UW/G8CoEEisr50+9ztGOCmurobbadHcs4LhwAC2s9SuYuCTTSK4c98ke901ks4Iho8TuG3WmBkgJlEsNMigMsnW6pwlx/Xnij06csRY8Xcfh77MjluQXx833oKRxffwC9NuIgXcBG0cqGraEF6RFxKlu3DsWRIElt5Rnju/WSNhDsWubKrMVtKXuYX0tTBn
template:
metadata:
creationTimestamp: null
labels:
allowedToBeCloned: "true"
name: renovate-env
namespace: renovate
type: Opaque

View File

@@ -1,17 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak
spec:
encryptedData:
admin-password: AgCPuUHRhzPEau3aH9UKq9gp+k5Pu+lPKizST30OWEOW9ozwdcHbq90eb3b5Sd+R/fRPkqta/8TrhZx3uzqNhVnMUSSwfcnxG14Nfq9yl/CAIuXBrPH1C+SghLWrGcv/CaKwT1arTk/ECjtz87vfpGXwqjjRc3rnH1fRjMTsIL6vwOLKIad6dXREzviUGoyxWp6U7A9QPUQqAApRY3zF5cSH603TYiSd8+EIEVViaAAQDg/2xaknbGsMi6M9Ei74IQIFoYRHarMnpkdn7AZnbX5u/bpIOmjMloUazVg8DhPHlc72QiRzqq95YFwfjMqJumOZwS/Fo4rQXoyKM5/aUe0j6zx0wgWk+MNHq7JUjdsJcGIrlgFW4Um9oDhZbYiexDmwG6cR/68fKLOoFpVx4gyZH7Is3+v6l/EYm+ssARU0e+28xA6ZVGm7YhsrgjLmPd3z3YmVafgFWpyEvIGkAKqHNmWD0txIZ3LWptISqwjcr7Yk63kPFjnuXS9t3nWOzSF/RXnLgM90ZSkoytZU+CwyjAHFf30qRWeMvnXB6Ry4g7Koz54jpoMj0Gk9Fqb0v60DgOd4yxhTbT91Z83x2SvKeucnbDB8IK4xMK+QNfKy20BbCHLGfY5kCdYxIUMNlB0LUsjDOnGjqCrAUZjkeHNI04jNvHYMItgP4/EgTU7NB1f/GsP6cwwdwbKmpHoFKGBPIPo5iMUKK51/U/da/1Zm3f9Qr1HrKQ==
password: AgACslplMgI3ZXPpx7BivIM8LIhNDD5j38wbzd4H6IKqZab/nTNpmz2Hc2hCD96H4ljhCIiw1mETzCWshu3vwvCVtJnH2pXiVY0nHpJcSH1C/HqBPOXWVEuEcm2EgvKnf8koKdtkHtAaT6egWrpvNEHCnapo1pa53qB1cZk0PbPQ+cIcedVTa6yF7VzFcOQp56mbtu8ez/iww+K/SdiXtp/IkVCaiOsC7j5ZUwZVmPB/w/nEjgPPETezy3OsOaAokelKq77EUfrj4mh2CEtK9bHBRkbmZkTc9pVkKC/SuWRud5S6xV/VcLRdy/EA/wuqj+Z/XF3hOqyHUpNblbntZ60lqwxoju0ZK9hgZWwZyNvzz04/gv0TkX3/W5JTxNwemJ32AjQ8dQZOz6gdkTsX68lT5P4YlR58Dxsb3WxQxG1ishkDlxGCBA2oQSyyIMFULETuu47V32N8FXOy5fgWHO5ZSer5yFjIIjgU7GyywiSesvZ1m9G7lB5EX56ay1djEmUPiKP0KDyb3QSgsaVbUEdTDi0bXXyDgoVBZEgQNS6vttd5b4qV/IGDqDB4g2hw5xLiff2vkF/h0QPGEz61O+FDyYP5m2rR37o5lD6yWJGI6cB/epoFTKVANPyyGdrIDKtVlgEE30w/fciq+FdDRZob7Yg4uzXi/kGvAYfBmi0Ek04EIU1FLdEq8p+5yZhDSBy4TWjTuH5JTvYlwHjFhnLrCHC/zCBSgkOPgUkAGNOHdTya2ElqEGSSfcRrrvMhnY6ZlZubhGFSdPSrZSXTauSo
postgres-password: AgB/qqkiqoQ1d5g+ZMF5Za0ZQ0kpD+6sh/ds9/uc5XBD4VkaX2+1EDrEcq776x/thbngR1Fi3HNBafRtflfptO64N0r3GBJiakVIT+fEdp7tJs6VKjqcP9HFAEObAVEdlcJzOm9h8iPvhi+kxGgkJYSP3ojJ/lfoWoMLtZjsOJtlQH8yw0uPd8xlgVukssxUzFKkT2RkihLwlK/Zn/8MX8BNJT3W9Li8ruK7pm0+qKqsPKcq0hpKHVRDyYpWubAmiGtj8+Oe3qSYoQxepIdUvtEgEXqHJ5DFggxYB4VzVErdKcyn2tK8JraofcOYpR+JM+8VRH/HWTp0/zNJzY8LCgN/V+KkPkCghVDryW+8ZS2FSP/bcqhPWVbQE3s9EblUny8htsOu2GikbbQcF4LzBKDhIJS6hc9SUbw5sfT7CtREcU7XCfq4VyX7dQgOdiGotO8OTmpXFZtceX9E8ClGTQ3V9tKZkzlpILkEip3PjbkkLshNTtJaRsFgRu37PDddUHaVczBoPKBelONC+KpKBQDoiTfB08BylNxdROsic2ag3upR5avOuqf0YDU6tDKBP3S0FPhPoZOmisUsiZNc8/QqV590gSUvqNBRDWWbqLtYsHmgeYRKDH02kiEzIBQuARryOjK8gOxmv0oo1SVdOd33Q1smU/W5YQNqtoVcqRQOtn0lR/cfQjtHJ3OSSNBZuvEx0BMUsLkH5Oud6nmnVITxT6gxLOS6K/BgV8WyUwgaO0r1sFdjiArCQcaDTjLHkAopYjgWsjEkV04XdjxssEJB
template:
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak