Initial commit

This commit is contained in:
Scooby Husky
2026-03-09 20:21:35 -05:00
commit aacb8eebbe
314 changed files with 21766 additions and 0 deletions
@@ -0,0 +1,34 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboards-provider
namespace: grafana
data:
dashboards.yaml: |
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: ''
type: file
disableDeletion: false
editable: true
options:
path: /var/lib/grafana/dashboards/default
- name: 'kubernetes'
orgId: 1
folder: 'Kubernetes'
type: file
disableDeletion: false
editable: true
options:
path: /var/lib/grafana/dashboards/kubernetes
- name: 'infrastructure'
orgId: 1
folder: 'Infrastructure'
type: file
disableDeletion: false
editable: true
options:
path: /var/lib/grafana/dashboards/infrastructure
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: grafana
namespace: grafana
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- grafana.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: grafana
port: 80
@@ -0,0 +1,12 @@
# infrastructure/grafana/grafana-oauth-secret.yaml
# Grafana OAuth secret - hardcoded from blueprint
apiVersion: v1
kind: Secret
metadata:
name: grafana-authentik-oauth
namespace: grafana
type: Opaque
stringData:
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "bd03e9139dd2063c6c44c4d2f65f51d69de3ba0b6d6b1b9b41c255d2376d2dcc"
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "6b6f2deecfe8fd56cae9c512cc71eedd463d67ff08f24c816b15b15e78ce36bc4f06a2276c5ffe3f67799d935994a32ed32cdc02ec674f279c82d7cfe3ca05d5"
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: grafana
namespace: grafana
spec:
hosts:
- grafana.kube.huskypup.net
gateways:
- istio-system/edge
http:
- route:
- destination:
host: grafana.grafana.svc.cluster.local
port:
number: 80
@@ -0,0 +1,11 @@
---
# Secret containing TeslaMate database password for Grafana datasource
# This is created by the Grafana presync hook from the CNPG secret
#
# The secret is referenced in values.yaml:
# envFromSecrets:
# - name: grafana-teslamate-datasource
#
# And used in the datasource configuration:
# secureJsonData:
# password: $__env{TESLAMATE_DB_PASSWORD}
@@ -0,0 +1,195 @@
---
# CronJob to sync TeslaMate database password from CNPG secret to Grafana datasource secret
# This ensures Grafana always has the current password even when CNPG rotates it
apiVersion: batch/v1
kind: CronJob
metadata:
name: sync-teslamate-password
namespace: grafana
spec:
# Run every 30 minutes to catch password rotations (rotations happen at most daily)
schedule: "*/30 * * * *"
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
metadata:
labels:
app: teslamate-password-sync
spec:
serviceAccountName: teslamate-password-sync
restartPolicy: OnFailure
containers:
- name: sync
image: docker.io/alpine/k8s:1.32.13
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp
env:
- name: GRAFANA_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: grafana-admin-secret
key: admin-password
optional: true
command:
- /bin/bash
- -c
- |
set -e
echo "Fetching current password from CNPG secret in teslamate namespace..."
CURRENT_PASSWORD=$(kubectl get secret -n teslamate pg-teslamate-app -o jsonpath='{.data.password}' | base64 -d)
echo "Fetching current password from Grafana datasource secret..."
GRAFANA_PASSWORD=$(kubectl get secret -n grafana grafana-teslamate-datasource -o jsonpath='{.data.TESLAMATE_DB_PASSWORD}' | base64 -d)
if [ "$CURRENT_PASSWORD" != "$GRAFANA_PASSWORD" ]; then
echo "Passwords differ - updating Grafana secret..."
kubectl create secret generic grafana-teslamate-datasource \
--from-literal=TESLAMATE_DB_PASSWORD="$CURRENT_PASSWORD" \
-n grafana \
--dry-run=client -o yaml | kubectl apply -f -
echo "Password synced to secret"
else
echo "Passwords match in secrets"
fi
echo "Finding running Grafana pod..."
GRAFANA_POD=$(kubectl get pod -n grafana -l app.kubernetes.io/name=grafana --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
if [ -z "$GRAFANA_POD" ]; then
echo "No running Grafana pod found, skipping API update"
exit 0
fi
echo "Using Grafana pod: $GRAFANA_POD"
echo "Updating Grafana datasource password via API (ensures password works after Grafana restart)..."
# Get admin password from Grafana secret (fallback to 'admin' if not found)
ADMIN_PASS=$(kubectl get secret -n grafana grafana -o jsonpath='{.data.admin-password}' 2>/dev/null | base64 -d || echo "admin")
# Update datasource via API with current password
RESULT=$(kubectl exec -n grafana "$GRAFANA_POD" -c grafana -- curl -s -X PUT \
-H "Content-Type: application/json" \
-u "admin:$ADMIN_PASS" \
http://localhost:3000/api/datasources/uid/TeslaMate \
-d "{
\"name\": \"TeslaMate\",
\"type\": \"grafana-postgresql-datasource\",
\"uid\": \"TeslaMate\",
\"url\": \"pg-teslamate-rw.teslamate.svc.cluster.local:5432\",
\"database\": \"teslamate\",
\"user\": \"teslamate\",
\"access\": \"proxy\",
\"isDefault\": false,
\"secureJsonData\": {
\"password\": \"$CURRENT_PASSWORD\"
},
\"jsonData\": {
\"sslmode\": \"disable\",
\"postgresVersion\": 1600,
\"timescaledb\": false,
\"database\": \"teslamate\"
}
}")
echo "API Response: $RESULT"
# Test datasource connection
echo "Testing datasource connection..."
TEST_RESULT=$(kubectl exec -n grafana "$GRAFANA_POD" -c grafana -- curl -s -X POST \
-u "admin:$ADMIN_PASS" \
http://localhost:3000/api/datasources/uid/TeslaMate/health)
echo "Health Check: $TEST_RESULT"
if echo "$TEST_RESULT" | grep -q '"status":"OK"'; then
echo "✅ Datasource password updated and verified successfully!"
else
echo "⚠️ Datasource updated but connection test failed"
exit 1
fi
echo "Done!"
volumes:
- name: tmp
emptyDir: {}
---
# ServiceAccount for the CronJob
apiVersion: v1
kind: ServiceAccount
metadata:
name: teslamate-password-sync
namespace: grafana
---
# Role with permissions to read CNPG secret and update Grafana secret
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: teslamate-password-sync
namespace: grafana
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "patch", "update"]
- apiGroups: [""]
resources: ["pods", "pods/exec"]
verbs: ["get", "list", "watch", "create"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "patch"]
---
# Role to read secret from teslamate namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: teslamate-password-sync
namespace: teslamate
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["pg-teslamate-app"]
verbs: ["get"]
---
# RoleBinding in grafana namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: teslamate-password-sync
namespace: grafana
subjects:
- kind: ServiceAccount
name: teslamate-password-sync
namespace: grafana
roleRef:
kind: Role
name: teslamate-password-sync
apiGroup: rbac.authorization.k8s.io
---
# RoleBinding in teslamate namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: teslamate-password-sync-grafana
namespace: teslamate
subjects:
- kind: ServiceAccount
name: teslamate-password-sync
namespace: grafana
roleRef:
kind: Role
name: teslamate-password-sync
apiGroup: rbac.authorization.k8s.io
+141
View File
@@ -0,0 +1,141 @@
# values/grafana.values.yaml
# --- Admin credentials via Vault/ESO ---
# Admin username & password are stored in Vault at:
# secret/grafana-admin
# and pulled into a K8s Secret grafana-admin-secret by ExternalSecret.
# admin:
# existingSecret: grafana-admin-secret
# userKey: admin-user
# passwordKey: admin-password
# These plain values are ignored when existingSecret is set, but leave
# them harmless defaults so you can still helm template without ESO.
adminUser: admin
adminPassword: "admin"
# --- Deployment annotations for Reloader ---
# Automatically restart Grafana when secrets change
# Note: Unpoller has its own Reloader annotation in unpoller namespace
deploymentAnnotations:
secret.reloader.stakater.com/reload: "grafana-authentik-oauth,grafana-teslamate-datasource"
# --- Persistence for dashboards and config ---
persistence:
enabled: true
type: pvc
storageClassName: rook-ceph-block
accessModes:
- ReadWriteOnce
size: 10Gi
# --- Service type ---
service:
type: ClusterIP
# Security context for Talos compatibility
securityContext:
runAsUser: 472
runAsGroup: 472
runAsNonRoot: true
fsGroup: 472
# --- Ingress disabled - Istio VirtualService handles routing ---
ingress:
enabled: false
# --- Authentik OIDC Integration ---
# ============================================================================
# AUTOMATIC ADMIN ACCESS - BOOTSTRAP READY
# ============================================================================
# Role mapping assigns Grafana roles based on Authentik group membership:
#
# Authentik Group → Grafana Role
# ─────────────────────────────────────────────────────────
# authentik Admins → Admin (full access) - AUTOMATIC!
# Grafana Admins → Admin (full access)
# Grafana Editors → Editor (can edit dashboards)
# Grafana Viewers → Viewer (read-only)
# (any other user) → Viewer (read-only)
#
# BOOTSTRAP BEHAVIOR:
# ✓ authentik Admins get automatic Grafana admin access (no manual config!)
# ✓ Groups auto-created by Authentik blueprint during bootstrap
# ✓ Add users to groups in Authentik UI for access control
#
# TO GRANT ADMIN ACCESS TO OTHER USERS:
# 1. Log into Authentik at https://auth.kube.huskypup.net
# 2. Go to Directory → Groups → "Grafana Admins"
# 3. Add users to the group
# 4. Users log out/in to Grafana to receive admin role
# ============================================================================
grafana.ini:
server:
root_url: https://grafana.kube.huskypup.net
auth.generic_oauth:
enabled: true
name: Authentik
scopes: openid profile email
auth_url: https://auth.kube.huskypup.net/application/o/authorize/
token_url: https://auth.kube.huskypup.net/application/o/token/
api_url: https://auth.kube.huskypup.net/application/o/userinfo/
# Role mapping: authentik Admins OR Grafana Admins → Admin, Grafana Editors → Editor, else → Viewer
role_attribute_path: contains(groups[*], 'authentik Admins') && 'Admin' || contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'
allow_sign_up: true
client_id: $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_ID}
client_secret: $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET}
# Load OAuth credentials and datasource passwords from secrets as environment variables
envFromSecrets:
- name: grafana-authentik-oauth
- name: grafana-teslamate-datasource
# --- Datasources ---
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
uid: Prometheus
url: http://kube-prometheus-stack-prometheus.prometheus.svc.cluster.local:9090
access: proxy
isDefault: true
- name: TeslaMate
type: grafana-postgresql-datasource
uid: TeslaMate
url: pg-teslamate-rw.teslamate.svc.cluster.local:5432
database: teslamate
user: teslamate
access: proxy
isDefault: false
editable: true
secureJsonData:
password: $__env{TESLAMATE_DB_PASSWORD}
jsonData:
sslmode: disable
postgresVersion: 1600
timescaledb: false
database: teslamate
# --- Sidecar to auto-discover dashboards from ConfigMaps ---
# Dashboards are deployed as ConfigMaps with the label grafana_dashboard: "1"
# The sidecar automatically discovers them and loads them into Grafana
containerSecurityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
sidecar:
dashboards:
enabled: true
label: grafana_dashboard
labelValue: "1"
folder: /var/lib/grafana/dashboards
searchNamespace: ALL
defaultFolderName: "General"
provider:
foldersFromFilesStructure: true