mirror of
https://gitlab.kube.huskypup.net/Scooby/Homelabv4.git
synced 2026-08-20 23:16:49 +00:00
Initial commit
This commit is contained in:
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# CRD versions (override via env vars for upgrades)
|
||||
PROMETHEUS_OPERATOR_VERSION="${PROMETHEUS_OPERATOR_VERSION:-v0.76.0}"
|
||||
GATEWAY_API_VERSION="${GATEWAY_API_VERSION:-v1.2.0}"
|
||||
|
||||
echo "=== Bootstrap: Installing required CRDs ==="
|
||||
|
||||
# Install Prometheus Operator CRDs (required before helmfile diff can run)
|
||||
echo "Installing Prometheus Operator CRDs (${PROMETHEUS_OPERATOR_VERSION})..."
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/${PROMETHEUS_OPERATOR_VERSION}/example/prometheus-operator-crd/monitoring.coreos.com_servicemonitors.yaml
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/${PROMETHEUS_OPERATOR_VERSION}/example/prometheus-operator-crd/monitoring.coreos.com_podmonitors.yaml
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/${PROMETHEUS_OPERATOR_VERSION}/example/prometheus-operator-crd/monitoring.coreos.com_prometheuses.yaml
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/${PROMETHEUS_OPERATOR_VERSION}/example/prometheus-operator-crd/monitoring.coreos.com_prometheusrules.yaml
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/${PROMETHEUS_OPERATOR_VERSION}/example/prometheus-operator-crd/monitoring.coreos.com_alertmanagers.yaml
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/${PROMETHEUS_OPERATOR_VERSION}/example/prometheus-operator-crd/monitoring.coreos.com_alertmanagerconfigs.yaml
|
||||
|
||||
echo "✅ Prometheus Operator CRDs installed successfully"
|
||||
|
||||
# Install Kyverno CRDs (required for PolicyReport resources)
|
||||
echo ""
|
||||
echo "Installing Kyverno PolicyReport CRDs..."
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/kubernetes-sigs/wg-policy-prototypes/master/policy-report/crd/v1beta1/wgpolicyk8s.io_clusterpolicyreports.yaml 2>/dev/null || echo "PolicyReport CRDs may already exist or URL changed - Kyverno will install them"
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/kubernetes-sigs/wg-policy-prototypes/master/policy-report/crd/v1beta1/wgpolicyk8s.io_policyreports.yaml 2>/dev/null || echo "PolicyReport CRDs may already exist or URL changed - Kyverno will install them"
|
||||
|
||||
echo "✅ Kyverno PolicyReport CRDs installed (or will be installed by Kyverno chart)"
|
||||
|
||||
# Install Gateway API CRDs (required for Istio ambient waypoint proxies)
|
||||
echo ""
|
||||
echo "Installing Gateway API CRDs (required for Istio ambient waypoint proxies)..."
|
||||
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION}/standard-install.yaml
|
||||
echo "✅ Gateway API CRDs installed successfully"
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
# Cilium Bootstrap - Install Cilium CNI before helmfile
|
||||
# Nodes require a CNI to become Ready. This script installs Cilium via Helm
|
||||
# and applies L2 announcement policies (replacing MetalLB).
|
||||
#
|
||||
# Usage: ./scripts/cilium-bootstrap.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "================================================="
|
||||
echo "Cilium CNI Bootstrap"
|
||||
echo "================================================="
|
||||
echo ""
|
||||
|
||||
# Add Cilium Helm repo
|
||||
echo "Adding Cilium Helm repository..."
|
||||
helm repo add cilium https://helm.cilium.io 2>/dev/null || true
|
||||
helm repo update cilium
|
||||
|
||||
# Check if Cilium is already installed
|
||||
if helm -n kube-system status cilium >/dev/null 2>&1; then
|
||||
echo "✅ Cilium is already installed"
|
||||
echo ""
|
||||
echo "Upgrading Cilium to match helmfile values..."
|
||||
helm upgrade cilium cilium/cilium \
|
||||
--namespace kube-system \
|
||||
--values infrastructure/cilium/values.yaml \
|
||||
--wait --timeout 300s
|
||||
else
|
||||
echo "Installing Cilium CNI..."
|
||||
helm install cilium cilium/cilium \
|
||||
--namespace kube-system \
|
||||
--values infrastructure/cilium/values.yaml \
|
||||
--wait --timeout 300s
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Waiting for Cilium to be ready..."
|
||||
kubectl -n kube-system rollout status daemonset/cilium --timeout=300s
|
||||
|
||||
echo ""
|
||||
echo "Applying L2 announcement policy and IP pool..."
|
||||
kubectl apply -f infrastructure/cilium/l2-announcement-policy.yaml
|
||||
|
||||
echo ""
|
||||
echo "Applying baseline network policies..."
|
||||
kubectl apply -f infrastructure/cilium/network-policies/dns.yaml
|
||||
|
||||
# Apply per-namespace policies only if namespaces exist
|
||||
for ns in vault authentik rook-ceph; do
|
||||
if kubectl get ns "$ns" >/dev/null 2>&1; then
|
||||
kubectl apply -f infrastructure/cilium/network-policies/baseline.yaml 2>/dev/null || true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Verifying Cilium status..."
|
||||
kubectl -n kube-system exec ds/cilium -- cilium status --brief 2>/dev/null || echo " (cilium CLI check will be available after pods stabilize)"
|
||||
|
||||
echo ""
|
||||
echo "Checking node readiness..."
|
||||
kubectl get nodes -o wide
|
||||
|
||||
echo ""
|
||||
echo "Verifying L2 announcements..."
|
||||
kubectl get ciliuml2announcementpolicy,ciliumloadbalancerippool 2>/dev/null || echo " (L2 CRDs being registered...)"
|
||||
|
||||
echo ""
|
||||
echo "================================================="
|
||||
echo "✅ Cilium CNI bootstrap complete"
|
||||
echo "================================================="
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# scripts/fix-gitlab-auth.sh
|
||||
# Fix GitLab authentication issues by refreshing passwords
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== GitLab Authentication Fix ==="
|
||||
echo "This script fixes the 500 errors caused by password synchronization issues"
|
||||
|
||||
# Function to restart deployment and wait
|
||||
restart_deployment() {
|
||||
local deployment=$1
|
||||
local namespace=$2
|
||||
|
||||
echo "Restarting $deployment in $namespace..."
|
||||
kubectl rollout restart deployment/$deployment -n $namespace
|
||||
kubectl rollout status deployment/$deployment -n $namespace --timeout=120s
|
||||
}
|
||||
|
||||
# 1. Refresh database secrets
|
||||
echo "Step 1: Refreshing database secrets..."
|
||||
kubectl delete secret pg-gitlab-app -n gitlab --ignore-not-found=true
|
||||
echo "Waiting for database secret to be recreated..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n gitlab get secret pg-gitlab-app >/dev/null 2>&1; then
|
||||
echo "✓ Database secret recreated"
|
||||
break
|
||||
fi
|
||||
echo " waiting... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 2. Refresh Redis secrets
|
||||
echo "Step 2: Refreshing Redis secrets..."
|
||||
kubectl delete secret redis-gitlab-secret -n gitlab --ignore-not-found=true
|
||||
echo "Waiting for Redis secret to be recreated..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n gitlab get secret redis-gitlab-secret >/dev/null 2>&1; then
|
||||
echo "✓ Redis secret recreated"
|
||||
break
|
||||
fi
|
||||
echo " waiting... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 3. Trigger PostgreSQL reload
|
||||
echo "Step 3: Reloading PostgreSQL clusters..."
|
||||
kubectl annotate cluster pg-gitlab -n gitlab cnpg.io/reload=$(date +%s) --overwrite
|
||||
kubectl annotate cluster pg-praefect -n gitlab cnpg.io/reload=$(date +%s) --overwrite
|
||||
|
||||
# 4. Restart Redis
|
||||
echo "Step 4: Restarting Redis..."
|
||||
kubectl rollout restart statefulset/redis-gitlab -n gitlab
|
||||
kubectl rollout status statefulset/redis-gitlab -n gitlab --timeout=120s
|
||||
|
||||
# 5. Restart GitLab services
|
||||
echo "Step 5: Restarting GitLab services..."
|
||||
restart_deployment "gitlab-webservice-default" "gitlab"
|
||||
restart_deployment "gitlab-sidekiq-all-in-1-v2" "gitlab"
|
||||
restart_deployment "gitlab-toolbox" "gitlab"
|
||||
|
||||
# 6. Verify GitLab is working
|
||||
echo "Step 6: Verifying GitLab functionality..."
|
||||
sleep 30
|
||||
|
||||
# Test GitLab health endpoint
|
||||
for i in {1..10}; do
|
||||
HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" https://gitlab.kube.huskypup.net/-/readiness)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "✓ GitLab is responding correctly"
|
||||
break
|
||||
fi
|
||||
echo " waiting for GitLab to be ready... (attempt $i/10) - HTTP $HTTP_CODE"
|
||||
sleep 10
|
||||
done
|
||||
|
||||
# Test main page
|
||||
HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" https://gitlab.kube.huskypup.net)
|
||||
if [ "$HTTP_CODE" = "302" ]; then
|
||||
echo "✓ GitLab main page is working (redirecting to login as expected)"
|
||||
elif [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "✓ GitLab main page is working"
|
||||
else
|
||||
echo "⚠ GitLab main page returned HTTP $HTTP_CODE (may still be starting)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "===================================================================="
|
||||
echo "GitLab Authentication Fix Complete"
|
||||
echo "===================================================================="
|
||||
echo "✓ Database secrets refreshed"
|
||||
echo "✓ Redis secrets refreshed"
|
||||
echo "✓ PostgreSQL clusters reloaded"
|
||||
echo "✓ Redis restarted"
|
||||
echo "✓ GitLab services restarted"
|
||||
echo ""
|
||||
echo "GitLab should now be accessible at: https://gitlab.kube.huskypup.net"
|
||||
echo "If you still see 500 errors, check the logs with:"
|
||||
echo " kubectl logs -n gitlab -l app=webservice --tail=20"
|
||||
echo "===================================================================="
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# gitlab-add-hostaliases.sh
|
||||
# Add hostAliases to GitLab deployments for Authentik OIDC
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Adding hostAliases to GitLab deployments ==="
|
||||
|
||||
NS=gitlab
|
||||
# Route via Istio ingressgateway LoadBalancer (ingress-nginx removed)
|
||||
EDGE_IP=$(kubectl get svc -n istio-system istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||
|
||||
echo "Istio edge gateway IP: $EDGE_IP"
|
||||
|
||||
# Check if webservice deployment already has hostAliases
|
||||
if kubectl get deployment gitlab-webservice-default -n "${NS}" -o jsonpath='{.spec.template.spec.hostAliases}' | grep -q "${EDGE_IP}"; then
|
||||
echo "✅ webservice already has hostAliases configured"
|
||||
else
|
||||
echo "Adding hostAliases to webservice..."
|
||||
kubectl patch deployment gitlab-webservice-default -n "${NS}" --type='json' -p="[{\"op\": \"add\", \"path\": \"/spec/template/spec/hostAliases\", \"value\": [{\"ip\": \"${EDGE_IP}\", \"hostnames\": [\"auth.kube.huskypup.net\"]}]}]"
|
||||
echo "✅ hostAliases added to webservice"
|
||||
fi
|
||||
|
||||
# Check if sidekiq deployment already has hostAliases
|
||||
if kubectl get deployment gitlab-sidekiq-all-in-1-v2 -n "${NS}" -o jsonpath='{.spec.template.spec.hostAliases}' | grep -q "${EDGE_IP}"; then
|
||||
echo "✅ sidekiq already has hostAliases configured"
|
||||
else
|
||||
echo "Adding hostAliases to sidekiq..."
|
||||
kubectl patch deployment gitlab-sidekiq-all-in-1-v2 -n "${NS}" --type='json' -p="[{\"op\": \"add\", \"path\": \"/spec/template/spec/hostAliases\", \"value\": [{\"ip\": \"${EDGE_IP}\", \"hostnames\": [\"auth.kube.huskypup.net\"]}]}]"
|
||||
echo "✅ hostAliases added to sidekiq"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== hostAliases configuration complete ==="
|
||||
echo "GitLab will now route auth.kube.huskypup.net through Istio edge gateway (${EDGE_IP})"
|
||||
echo "This ensures proper SSL certificate validation for Authentik OIDC"
|
||||
Executable
+354
@@ -0,0 +1,354 @@
|
||||
#!/bin/bash
|
||||
# scripts/gitlab-bootstrap.sh
|
||||
# GitLab presync bootstrap script - fully automated, no manual interaction required
|
||||
# Auto-creates OAuth provider in Authentik and syncs credentials to GitLab
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== GitLab Bootstrap - Fully Automated with Authentik Integration ==="
|
||||
echo "Setting up GitLab infrastructure with auto-generated secrets..."
|
||||
|
||||
# Change to the apps directory for relative paths
|
||||
cd "$(dirname "$0")/../apps" || exit 1
|
||||
|
||||
# Ensure namespace exists
|
||||
kubectl get ns gitlab >/dev/null 2>&1 || kubectl create ns gitlab
|
||||
|
||||
# ============================================================================
|
||||
# Step 1: Ensure Authentik has GitLab OAuth provider
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo "Step 1: Configuring Authentik OAuth provider for GitLab..."
|
||||
|
||||
# Apply the Authentik blueprint (if not already applied)
|
||||
kubectl apply -f ../infrastructure/authentik/gitlab-blueprint.yaml 2>/dev/null || true
|
||||
|
||||
# Give Authentik time to process the blueprint (it auto-discovers ConfigMaps with the label)
|
||||
echo "Waiting for Authentik to process GitLab blueprint..."
|
||||
sleep 10
|
||||
|
||||
# ============================================================================
|
||||
# Step 2: Deploy PostgreSQL clusters
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo "Step 2: Deploying PostgreSQL clusters..."
|
||||
|
||||
# Apply CloudNativePG PostgreSQL clusters
|
||||
# NOTE: CNPG auto-generates database passwords in secrets like pg-gitlab-app
|
||||
echo "Applying GitLab PostgreSQL CNPG cluster..."
|
||||
kubectl apply -f gitlab/cnpg-cluster.yaml
|
||||
|
||||
echo "Applying Praefect PostgreSQL CNPG cluster..."
|
||||
kubectl apply -f gitlab/praefect-cnpg-cluster.yaml
|
||||
|
||||
# Apply PgBouncer poolers
|
||||
echo "Applying PgBouncer connection poolers..."
|
||||
kubectl apply -f gitlab/pgbouncer-pooler.yaml
|
||||
|
||||
# Wait for PostgreSQL clusters to be ready
|
||||
echo "Waiting for PostgreSQL clusters to be ready..."
|
||||
for i in {1..60}; do
|
||||
READY=$(kubectl -n gitlab get cluster pg-gitlab -o jsonpath='{.status.instances}' 2>/dev/null || echo "0")
|
||||
if [ "$READY" -ge "1" ]; then
|
||||
echo "GitLab PostgreSQL cluster has $READY instance(s) ready!"
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
for i in {1..60}; do
|
||||
READY=$(kubectl -n gitlab get cluster pg-praefect -o jsonpath='{.status.instances}' 2>/dev/null || echo "0")
|
||||
if [ "$READY" -ge "1" ]; then
|
||||
echo "Praefect PostgreSQL cluster has $READY instance(s) ready!"
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# ============================================================================
|
||||
# Step 3: Deploy Redis
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo "Step 3: Deploying Redis..."
|
||||
|
||||
# Apply Redis standalone instance and auto-restart automation
|
||||
echo "Applying GitLab Redis standalone instance..."
|
||||
kubectl apply -f gitlab/redis-cluster.yaml
|
||||
|
||||
echo "Applying GitLab Redis auto-restart automation..."
|
||||
kubectl apply -f gitlab/redis-auto-restart.yaml
|
||||
|
||||
echo "Applying GitLab PostgreSQL auto-restart automation..."
|
||||
kubectl apply -f gitlab/pg-auto-restart.yaml
|
||||
|
||||
# Verify auto-restart CronJobs were created
|
||||
echo "Verifying auto-restart CronJobs..."
|
||||
for i in {1..10}; do
|
||||
REDIS_OK=false
|
||||
PG_GITLAB_OK=false
|
||||
PG_PRAEFECT_OK=false
|
||||
|
||||
if kubectl get cronjob -n gitlab redis-secret-monitor >/dev/null 2>&1; then
|
||||
REDIS_OK=true
|
||||
fi
|
||||
if kubectl get cronjob -n gitlab pg-gitlab-secret-monitor >/dev/null 2>&1; then
|
||||
PG_GITLAB_OK=true
|
||||
fi
|
||||
if kubectl get cronjob -n gitlab pg-praefect-secret-monitor >/dev/null 2>&1; then
|
||||
PG_PRAEFECT_OK=true
|
||||
fi
|
||||
|
||||
if $REDIS_OK && $PG_GITLAB_OK && $PG_PRAEFECT_OK; then
|
||||
echo "✓ All auto-restart CronJobs are deployed!"
|
||||
break
|
||||
fi
|
||||
echo " waiting for CronJobs to be created... (attempt $i/10)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Apply Redis Sentinel HA cluster
|
||||
echo "Applying GitLab Redis Sentinel HA..."
|
||||
kubectl apply -f gitlab/redis-sentinel-ha.yaml
|
||||
|
||||
# Wait for Redis to be ready
|
||||
echo "Waiting for Redis Sentinel cluster to be ready..."
|
||||
kubectl wait --for=condition=ready pod -n gitlab -l app=redis-gitlab-ha --timeout=120s 2>/dev/null || echo "Redis may still be starting..."
|
||||
|
||||
# ============================================================================
|
||||
# Step 4: Set up PgBouncer authentication (SCRAM-SHA-256)
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo "Step 4: Setting up PgBouncer authentication with SCRAM-SHA-256..."
|
||||
|
||||
# Get the primary PostgreSQL pod (read-write)
|
||||
PRIMARY_POD=$(kubectl get pod -n gitlab -l cnpg.io/cluster=pg-gitlab,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$PRIMARY_POD" ]; then
|
||||
echo "❌ ERROR: Could not find primary PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using primary pod: $PRIMARY_POD"
|
||||
|
||||
# Create table for password hashes (needed for PgBouncer SCRAM-SHA-256 auth)
|
||||
echo "Creating user passwords table for PgBouncer..."
|
||||
kubectl exec -n gitlab "$PRIMARY_POD" -- psql -U postgres -c "CREATE TABLE IF NOT EXISTS public.user_passwords (usename text primary key, passwd text);"
|
||||
|
||||
# Create user_search function to return password hashes from the table
|
||||
echo "Creating user_search function for SCRAM-SHA-256 authentication..."
|
||||
kubectl exec -n gitlab "$PRIMARY_POD" -- psql -U postgres -c "DROP FUNCTION IF EXISTS public.user_search(text);"
|
||||
kubectl exec -n gitlab "$PRIMARY_POD" -- psql -U postgres -c "CREATE FUNCTION public.user_search(uname text) RETURNS TABLE(usename text, passwd text) AS \$\$ SELECT usename, passwd FROM public.user_passwords WHERE usename = \$1; \$\$ LANGUAGE sql SECURITY DEFINER;"
|
||||
|
||||
# Wait for PgBouncer pooler to create its role
|
||||
echo "Waiting for PgBouncer pooler pods to be ready..."
|
||||
for i in {1..30}; do
|
||||
POOLER_READY=$(kubectl get pods -n gitlab -l cnpg.io/poolerName=pgbouncer-gitlab --no-headers 2>/dev/null | grep -c Running || echo "0")
|
||||
if [ "$POOLER_READY" -ge "1" ]; then
|
||||
echo "✅ PgBouncer pooler pods are running"
|
||||
break
|
||||
fi
|
||||
echo " waiting for pooler pods... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Grant permissions to PgBouncer auth user (create role if it doesn't exist)
|
||||
echo "Granting permissions to PgBouncer auth user..."
|
||||
kubectl exec -n gitlab "$PRIMARY_POD" -- psql -U postgres -c "DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'cnpg_pooler_pgbouncer') THEN CREATE ROLE cnpg_pooler_pgbouncer LOGIN; END IF; END \$\$;" 2>/dev/null || echo " ↳ Role may already exist"
|
||||
kubectl exec -n gitlab "$PRIMARY_POD" -- psql -U postgres -c "GRANT EXECUTE ON FUNCTION public.user_search(text) TO cnpg_pooler_pgbouncer;" 2>/dev/null || echo " ↳ Permission may already be granted"
|
||||
|
||||
# Sync password hash from pg_authid to user_passwords table
|
||||
# This ensures PgBouncer can authenticate using SCRAM-SHA-256
|
||||
echo "Syncing password hash from pg_authid to user_passwords table..."
|
||||
kubectl exec -n gitlab "$PRIMARY_POD" -- psql -U postgres -c "INSERT INTO public.user_passwords (usename, passwd) SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'app' ON CONFLICT (usename) DO UPDATE SET passwd = EXCLUDED.passwd;"
|
||||
|
||||
# Verify the password hash was stored correctly
|
||||
echo "Verifying password hash sync..."
|
||||
HASH_COUNT=$(kubectl exec -n gitlab "$PRIMARY_POD" -- psql -U postgres -t -c "SELECT COUNT(*) FROM public.user_passwords WHERE usename = 'app' AND passwd LIKE 'SCRAM-SHA-256%';" | tr -d ' ')
|
||||
|
||||
if [ "$HASH_COUNT" = "1" ]; then
|
||||
echo "✅ PgBouncer authentication configured with SCRAM-SHA-256"
|
||||
else
|
||||
echo "⚠️ WARNING: Password hash may not be correctly stored"
|
||||
echo " PgBouncer authentication may fail - check user_passwords table"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Step 5: Configure OAuth/SAML credentials
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo "Step 5: Configuring OAuth and SAML credentials..."
|
||||
|
||||
# Try to sync OAuth credentials from Authentik to Vault
|
||||
echo "Attempting to sync OAuth credentials from Authentik..."
|
||||
if bash ../scripts/sync-gitlab-oauth.sh 2>&1 | grep -q "Successfully stored"; then
|
||||
echo "✓ Successfully synced OAuth credentials from Authentik to Vault"
|
||||
OAUTH_SYNCED=true
|
||||
else
|
||||
echo "⚠ Could not sync OAuth from Authentik (provider may not be ready yet)"
|
||||
echo " GitLab will use placeholder credentials - run './scripts/sync-gitlab-oauth.sh' later to enable SSO"
|
||||
OAUTH_SYNCED=false
|
||||
fi
|
||||
|
||||
# Apply External Secrets for GitLab OIDC (will sync from Vault if available)
|
||||
echo "Applying GitLab OIDC External Secrets..."
|
||||
kubectl apply -f gitlab/external-secret.yaml
|
||||
|
||||
# Apply External Secrets for GitLab SAML (optional)
|
||||
echo "Applying GitLab SAML External Secrets..."
|
||||
kubectl apply -f gitlab/external-secret-saml.yaml 2>/dev/null || true
|
||||
|
||||
# Wait for External Secret to sync (with timeout)
|
||||
echo "Checking if GitLab OIDC External Secret can sync from Vault..."
|
||||
OIDC_SYNCED=false
|
||||
for i in {1..10}; do
|
||||
STATUS=$(kubectl -n gitlab get externalsecret gitlab-oidc -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "False")
|
||||
if [ "$STATUS" = "True" ]; then
|
||||
echo "✓ GitLab OIDC External Secret synced from Vault!"
|
||||
OIDC_SYNCED=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Create placeholder OAuth credentials in Vault if they don't exist
|
||||
echo "Checking if OAuth credentials exist in Vault..."
|
||||
VAULT_POD=$(kubectl get pods -n vault -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
VAULT_TOKEN=$(kubectl get secret -n vault vault-init-keys -o jsonpath='{.data.VAULT_ROOT_TOKEN}' | base64 -d)
|
||||
|
||||
if ! kubectl exec -n vault "$VAULT_POD" -- env VAULT_TOKEN="$VAULT_TOKEN" vault kv get secret/gitlab-oauth >/dev/null 2>&1; then
|
||||
echo "⚠ Vault doesn't have gitlab-oauth credentials yet. Creating placeholders..."
|
||||
PLACEHOLDER_SECRET=$(openssl rand -hex 32)
|
||||
kubectl exec -n vault "$VAULT_POD" -- env VAULT_TOKEN="$VAULT_TOKEN" vault kv put secret/gitlab-oauth \
|
||||
client-id="placeholder-gitlab-client-id" \
|
||||
client-secret="$PLACEHOLDER_SECRET"
|
||||
echo "✓ Placeholder OAuth credentials created in Vault"
|
||||
echo " Update with real credentials: ./scripts/sync-gitlab-oauth.sh"
|
||||
else
|
||||
echo "✓ GitLab OAuth credentials exist in Vault"
|
||||
fi
|
||||
|
||||
# Wait for external secret to sync the placeholder/real credentials
|
||||
echo "Waiting for GitLab OIDC External Secret to sync..."
|
||||
for i in {1..30}; do
|
||||
STATUS=$(kubectl -n gitlab get externalsecret gitlab-oidc -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "False")
|
||||
if [ "$STATUS" = "True" ]; then
|
||||
echo "✓ GitLab OIDC External Secret synced successfully"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Check if SAML secret can sync from Vault
|
||||
echo "Checking if GitLab SAML External Secret can sync from Vault..."
|
||||
SAML_SYNCED=false
|
||||
for i in {1..10}; do
|
||||
STATUS=$(kubectl -n gitlab get externalsecret gitlab-saml -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "False")
|
||||
if [ "$STATUS" = "True" ]; then
|
||||
echo "✓ GitLab SAML External Secret synced from Vault!"
|
||||
SAML_SYNCED=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Create placeholder SAML credentials in Vault if they don't exist
|
||||
echo "Checking if SAML credentials exist in Vault..."
|
||||
if ! kubectl exec -n vault "$VAULT_POD" -- env VAULT_TOKEN="$VAULT_TOKEN" vault kv get secret/gitlab/saml >/dev/null 2>&1; then
|
||||
echo "⚠ Vault doesn't have gitlab/saml credentials yet. Creating placeholders..."
|
||||
kubectl exec -n vault "$VAULT_POD" -- env VAULT_TOKEN="$VAULT_TOKEN" vault kv put secret/gitlab/saml \
|
||||
idp_sso_url="https://auth.kube.huskypup.net/application/saml/gitlab/sso/binding/redirect/" \
|
||||
idp_fingerprint="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
|
||||
echo "✓ Placeholder SAML credentials created in Vault"
|
||||
echo " Update with real credentials: ./scripts/gitlab-saml-bootstrap.sh"
|
||||
else
|
||||
echo "✓ GitLab SAML credentials exist in Vault"
|
||||
fi
|
||||
|
||||
# Wait for external secret to sync
|
||||
echo "Waiting for GitLab SAML External Secret to sync..."
|
||||
for i in {1..30}; do
|
||||
STATUS=$(kubectl -n gitlab get externalsecret gitlab-saml -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "False")
|
||||
if [ "$STATUS" = "True" ]; then
|
||||
echo "✓ GitLab SAML External Secret synced successfully"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Force password synchronization to prevent authentication issues
|
||||
echo "=== Synchronizing Database and Redis Passwords ==="
|
||||
|
||||
# Delete and recreate database secrets to ensure sync
|
||||
echo "Refreshing database secrets..."
|
||||
kubectl delete secret pg-gitlab-app -n gitlab --ignore-not-found=true
|
||||
kubectl delete secret pg-praefect-app -n gitlab --ignore-not-found=true
|
||||
|
||||
# Wait for ExternalSecrets to recreate secrets
|
||||
echo "Waiting for database secrets to be recreated..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n gitlab get secret pg-gitlab-app >/dev/null 2>&1 && \
|
||||
kubectl -n gitlab get secret pg-praefect-app >/dev/null 2>&1; then
|
||||
echo "Database secrets recreated successfully!"
|
||||
break
|
||||
fi
|
||||
echo " waiting for database secrets... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Delete and recreate Redis secret to ensure sync
|
||||
echo "Refreshing Redis secrets..."
|
||||
kubectl delete secret redis-gitlab-secret -n gitlab --ignore-not-found=true
|
||||
|
||||
# Wait for Redis ExternalSecret to recreate secret
|
||||
echo "Waiting for Redis secret to be recreated..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n gitlab get secret redis-gitlab-secret >/dev/null 2>&1; then
|
||||
echo "Redis secret recreated successfully!"
|
||||
break
|
||||
fi
|
||||
echo " waiting for Redis secret... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Trigger PostgreSQL reload to pick up new passwords
|
||||
echo "Triggering PostgreSQL cluster reload..."
|
||||
kubectl annotate cluster pg-gitlab -n gitlab cnpg.io/reload=$(date +%s) --overwrite
|
||||
kubectl annotate cluster pg-praefect -n gitlab cnpg.io/reload=$(date +%s) --overwrite
|
||||
|
||||
echo ""
|
||||
echo "===================================================================="
|
||||
echo "GitLab Bootstrap Summary - Fully Automated"
|
||||
echo "===================================================================="
|
||||
echo "✓ Authentik GitLab OAuth blueprint applied"
|
||||
echo "✓ PostgreSQL CNPG clusters (GitLab + Praefect) deployed"
|
||||
echo "✓ PgBouncer connection poolers configured with authentication"
|
||||
echo "✓ Redis standalone instance deployed"
|
||||
echo "✓ Redis auto-restart automation (CronJob) deployed"
|
||||
echo "✓ Redis Sentinel HA cluster deployed"
|
||||
|
||||
if [ "${OAUTH_SYNCED:-false}" = "true" ]; then
|
||||
echo "✓ GitLab OIDC credentials synced from Authentik"
|
||||
else
|
||||
echo "⚠ GitLab OIDC using placeholder (Authentik provider not ready)"
|
||||
fi
|
||||
|
||||
echo "✓ GitLab SAML secret configured (placeholder)"
|
||||
echo "✓ Database and Redis passwords synchronized"
|
||||
echo ""
|
||||
echo "Password Rotation: Automated via CronJob (hourly checks)"
|
||||
echo "Next password rotation: $(kubectl get externalsecret -n gitlab gitlab-redis-password -o jsonpath='{.status.refreshTime}' 2>/dev/null || echo 'Unknown') + 24h"
|
||||
echo "===================================================================="
|
||||
echo ""
|
||||
echo "🎉 GitLab bootstrap completed - NO MANUAL STEPS REQUIRED!"
|
||||
echo ""
|
||||
echo "GitLab will be accessible at: https://gitlab.kube.huskypup.net"
|
||||
echo ""
|
||||
if [ "${OAUTH_SYNCED:-false}" = "false" ]; then
|
||||
echo "To enable Authentik SSO (optional):"
|
||||
echo " 1. Wait for Authentik to process the GitLab blueprint (~5 min)"
|
||||
echo " 2. Run: ./scripts/sync-gitlab-oauth.sh"
|
||||
echo " 3. Restart GitLab pods to pick up real credentials"
|
||||
echo ""
|
||||
fi
|
||||
echo "For SAML setup (optional): ./scripts/gitlab-saml-bootstrap.sh"
|
||||
echo ""
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
# Grant GitLab admin access to users in Authentik "authentik Admins" group
|
||||
# Run this after users login via Authentik SSO
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ADMIN_GROUP="authentik Admins"
|
||||
|
||||
echo "🔄 Granting GitLab admin access to Authentik admin group members..."
|
||||
echo ""
|
||||
|
||||
# Get Authentik database password
|
||||
PGPASSWORD=$(kubectl get secret -n authentik pg-authentik-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Query Authentik for users in admin group
|
||||
echo "📋 Getting users from Authentik '$ADMIN_GROUP' group..."
|
||||
ADMIN_EMAILS=$(kubectl exec -n authentik pg-authentik-1 -- env PGPASSWORD="$PGPASSWORD" psql -h pg-authentik-rw -U app -d app -t -c "
|
||||
SELECT DISTINCT u.email
|
||||
FROM authentik_core_user u
|
||||
JOIN authentik_core_user_groups ug ON u.id = ug.user_id
|
||||
JOIN authentik_core_group g ON ug.group_id = g.group_uuid
|
||||
WHERE g.name = '$ADMIN_GROUP' AND u.is_active = true;
|
||||
" 2>&1 | grep -v "Defaulted" | grep '@' | xargs)
|
||||
|
||||
if [ -z "$ADMIN_EMAILS" ]; then
|
||||
echo "⚠️ No users found in Authentik '$ADMIN_GROUP' group"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✓ Found admin users: $ADMIN_EMAILS"
|
||||
echo ""
|
||||
|
||||
# Check if gitlab-toolbox pod exists
|
||||
TOOLBOX_POD=$(kubectl get pods -n gitlab -l app=toolbox,release=gitlab -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$TOOLBOX_POD" ]; then
|
||||
echo "❌ GitLab toolbox pod not found"
|
||||
echo " Toolbox is required to run Rails commands"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔧 Using toolbox pod: $TOOLBOX_POD"
|
||||
echo ""
|
||||
|
||||
# For each admin user, grant admin access
|
||||
for email in $ADMIN_EMAILS; do
|
||||
echo "🔐 Processing: $email"
|
||||
|
||||
kubectl exec -n gitlab "$TOOLBOX_POD" -- gitlab-rails runner "
|
||||
user = User.find_by(email: '$email')
|
||||
if user
|
||||
if user.admin?
|
||||
puts ' ✓ Already admin'
|
||||
else
|
||||
user.update!(admin: true)
|
||||
puts ' ✅ Promoted to admin'
|
||||
end
|
||||
else
|
||||
puts ' ⚠️ User not found (needs to login via Authentik SSO first)'
|
||||
end
|
||||
" 2>&1 | grep -v "^$"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✅ Admin sync complete!"
|
||||
echo ""
|
||||
echo "💡 Note: Users must login via Authentik SSO at least once before they can be promoted"
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# scripts/grafana-bootstrap.sh
|
||||
# Grafana presync bootstrap script
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Grafana Bootstrap ==="
|
||||
|
||||
# Ensure namespace exists
|
||||
kubectl get ns grafana >/dev/null 2>&1 || kubectl create ns grafana
|
||||
|
||||
# Apply External Secrets for Grafana OAuth
|
||||
echo "Applying Grafana OAuth External Secret..."
|
||||
kubectl apply -f ../base/external-secrets.yaml || echo "Warning: Some external secrets failed to apply (expected if namespaces don't exist)"
|
||||
|
||||
# Wait for External Secret to sync
|
||||
echo "Waiting for Grafana OAuth External Secret to sync..."
|
||||
for i in {1..30}; do
|
||||
STATUS=$(kubectl -n grafana get externalsecret grafana-oauth -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "False")
|
||||
if [ "$STATUS" = "True" ]; then
|
||||
echo "Grafana OAuth External Secret is ready!"
|
||||
break
|
||||
fi
|
||||
echo " waiting for External Secret to be ready... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Verify secret was created
|
||||
if ! kubectl -n grafana get secret grafana-authentik-oauth >/dev/null 2>&1; then
|
||||
echo "WARNING: grafana-authentik-oauth secret not found. Ensure Vault has grafana-oauth credentials stored."
|
||||
echo "Run: ../../scripts/sync-grafana-oauth.sh"
|
||||
else
|
||||
echo "Grafana OAuth secret successfully synced!"
|
||||
fi
|
||||
|
||||
# Apply dashboard ConfigMaps
|
||||
echo "Applying Grafana dashboard ConfigMaps..."
|
||||
kubectl apply -f grafana/dashboards/
|
||||
|
||||
echo "✅ Grafana bootstrap complete"
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Health check script for Kubernetes cluster
|
||||
|
||||
echo "======================================"
|
||||
echo " Kubernetes Cluster Health Check"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
echo "=== Node Resources ==="
|
||||
kubectl top nodes 2>&1 || echo "❌ Metrics server not working!"
|
||||
echo ""
|
||||
|
||||
echo "=== High Memory Nodes (>85%) ==="
|
||||
kubectl top nodes --no-headers | awk '$5 > 85 {print "⚠️ "$1" - "$5"% memory"}'
|
||||
echo ""
|
||||
|
||||
echo "=== Pods without Resource Limits ==="
|
||||
COUNT=$(kubectl get pods -A -o json | jq -r '.items[] | select(.spec.containers[].resources.limits == null) | .metadata.namespace + "/" + .metadata.name' 2>/dev/null | wc -l)
|
||||
echo "$COUNT pods without resource limits"
|
||||
echo ""
|
||||
|
||||
echo "=== Failing Pods ==="
|
||||
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded 2>/dev/null | grep -v "No resources found" || echo "✅ All pods running"
|
||||
echo ""
|
||||
|
||||
echo "=== HPA Status ==="
|
||||
kubectl get hpa -A 2>/dev/null | grep -v "No resources found" || echo "ℹ️ No HPAs configured"
|
||||
echo ""
|
||||
|
||||
echo "=== Pods with High Restarts (>10) ==="
|
||||
kubectl get pods -A -o json | jq -r '.items[] | select(.status.containerStatuses != null) | select(.status.containerStatuses[].restartCount > 10) | .metadata.namespace + "/" + .metadata.name + " - " + (.status.containerStatuses[].restartCount|tostring) + " restarts"' 2>/dev/null || echo "✅ No pods with excessive restarts"
|
||||
echo ""
|
||||
|
||||
echo "=== Storage Status ==="
|
||||
kubectl get pv | grep -c "Bound"
|
||||
echo "persistent volumes bound"
|
||||
echo ""
|
||||
|
||||
echo "=== Ingress Status ==="
|
||||
kubectl get ingress -A | tail -n +2 | wc -l
|
||||
echo "ingresses configured"
|
||||
echo ""
|
||||
|
||||
echo "======================================"
|
||||
echo " Health Check Complete"
|
||||
echo "======================================"
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# Home Assistant Bootstrap - Auto-configure OIDC with hass-openid integration
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Home Assistant Bootstrap - Fully Automated ==="
|
||||
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
|
||||
NS=home-assistant
|
||||
|
||||
# 1) Ensure namespace
|
||||
kubectl get ns "${NS}" >/dev/null 2>&1 || kubectl create ns "${NS}"
|
||||
|
||||
# 2) Apply ExternalSecret for OIDC credentials
|
||||
echo "Applying Home Assistant OIDC ExternalSecret..."
|
||||
kubectl apply -f apps/home-assistant/external-secret.yaml
|
||||
|
||||
# 3) Wait for ESO to sync OIDC credentials
|
||||
echo "Waiting for OIDC credentials to sync from Vault..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n "${NS}" get secret homeassistant-oidc-secret >/dev/null 2>&1; then
|
||||
echo "OIDC credentials synced successfully"
|
||||
break
|
||||
fi
|
||||
echo " waiting... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 4) Wait for the PVC to be bound
|
||||
echo "Waiting for PVC to be bound..."
|
||||
for i in {1..60}; do
|
||||
if kubectl -n "${NS}" get pvc home-assistant-config >/dev/null 2>&1; then
|
||||
PVC_STATUS=$(kubectl -n "${NS}" get pvc home-assistant-config -o jsonpath='{.status.phase}')
|
||||
if [ "${PVC_STATUS}" = "Bound" ]; then
|
||||
echo "PVC is bound"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
echo " waiting for PVC... (attempt $i/60)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 5) Get the running Home Assistant pod (if any)
|
||||
POD_NAME=$(kubectl get pods -n "${NS}" -l app.kubernetes.io/name=home-assistant -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "${POD_NAME}" ] && kubectl -n "${NS}" get pod "${POD_NAME}" >/dev/null 2>&1; then
|
||||
echo "Configuring existing Home Assistant pod: ${POD_NAME}"
|
||||
|
||||
# Get OIDC credentials
|
||||
CLIENT_ID=$(kubectl get secret -n "${NS}" homeassistant-oidc-secret -o jsonpath='{.data.client_id}' | base64 -d)
|
||||
CLIENT_SECRET=$(kubectl get secret -n "${NS}" homeassistant-oidc-secret -o jsonpath='{.data.client_secret}' | base64 -d)
|
||||
|
||||
# Create secrets.yaml
|
||||
echo "Creating secrets.yaml..."
|
||||
kubectl exec -n "${NS}" "${POD_NAME}" -- sh -c "cat > /config/secrets.yaml <<EOF
|
||||
oidc_client_id: \"${CLIENT_ID}\"
|
||||
oidc_client_secret: \"${CLIENT_SECRET}\"
|
||||
EOF"
|
||||
|
||||
# Create oidc.yaml using Python to avoid YAML tag issues
|
||||
echo "Creating oidc.yaml..."
|
||||
kubectl exec -n "${NS}" "${POD_NAME}" -- python3 -c "
|
||||
import os
|
||||
oidc_content = '''openid:
|
||||
client_id: \"!secret oidc_client_id\"
|
||||
client_secret: \"!secret oidc_client_secret\"
|
||||
configure_url: \"https://auth.kube.huskypup.net/application/o/home-assistant/.well-known/openid-configuration\"
|
||||
scope: \"openid profile email\"
|
||||
username_field: \"preferred_username\"
|
||||
block_login: false'''
|
||||
with open('/config/oidc.yaml', 'w') as f:
|
||||
f.write(oidc_content)
|
||||
"
|
||||
|
||||
# Update configuration.yaml if needed
|
||||
echo "Updating configuration.yaml..."
|
||||
kubectl exec -n "${NS}" "${POD_NAME}" -- sh -c "
|
||||
if ! grep -q 'packages:' /config/configuration.yaml 2>/dev/null; then
|
||||
echo '' >> /config/configuration.yaml
|
||||
echo 'homeassistant:' >> /config/configuration.yaml
|
||||
echo ' packages: !include_dir_merge_named oidc' >> /config/configuration.yaml
|
||||
fi
|
||||
|
||||
if ! grep -q 'use_x_forwarded_for:' /config/configuration.yaml 2>/dev/null; then
|
||||
cat >> /config/configuration.yaml <<'EOF'
|
||||
|
||||
http:
|
||||
use_x_forwarded_for: true
|
||||
trusted_proxies:
|
||||
- 10.0.0.0/8
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
- 127.0.0.1
|
||||
- ::1
|
||||
EOF
|
||||
fi
|
||||
"
|
||||
|
||||
echo "Configuration complete. Restarting Home Assistant..."
|
||||
kubectl rollout restart deployment -n "${NS}" home-assistant
|
||||
kubectl rollout status deployment -n "${NS}" home-assistant --timeout=300s
|
||||
else
|
||||
echo "No running Home Assistant pod found. Configuration will be applied when pod starts."
|
||||
echo "The initContainer will install hass-openid, and the configuration will be set up on first boot."
|
||||
fi
|
||||
|
||||
echo "Home Assistant bootstrap complete!"
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Import Grafana dashboards from grafana.com
|
||||
# Usage: ./import-grafana-dashboards.sh
|
||||
|
||||
GRAFANA_URL="http://localhost:3000"
|
||||
GRAFANA_USER="admin"
|
||||
GRAFANA_PASSWORD=$(kubectl get secret -n grafana grafana -o jsonpath="{.data.admin-password}" | base64 --decode)
|
||||
|
||||
# Dashboard IDs and their folders
|
||||
declare -A DASHBOARDS
|
||||
DASHBOARDS[7249]="Kubernetes" # K8s Cluster
|
||||
DASHBOARDS[11663]="Kubernetes" # K8s Resources Cluster
|
||||
DASHBOARDS[11664]="Kubernetes" # K8s Resources Namespace
|
||||
DASHBOARDS[11665]="Kubernetes" # K8s Resources Pod
|
||||
DASHBOARDS[11001]="Infrastructure" # Cert-Manager
|
||||
DASHBOARDS[16888]="Infrastructure" # Longhorn
|
||||
DASHBOARDS[9628]="Infrastructure" # PostgreSQL
|
||||
DASHBOARDS[14584]="Infrastructure" # ArgoCD
|
||||
|
||||
echo "Starting port-forward to Grafana..."
|
||||
kubectl port-forward -n grafana svc/grafana 3000:80 &
|
||||
PF_PID=$!
|
||||
sleep 5
|
||||
|
||||
# Create folders
|
||||
echo "Creating folders..."
|
||||
for folder in "Kubernetes" "Infrastructure"; do
|
||||
curl -s -X POST -H "Content-Type: application/json" \
|
||||
-u "${GRAFANA_USER}:${GRAFANA_PASSWORD}" \
|
||||
-d "{\"title\":\"${folder}\"}" \
|
||||
"${GRAFANA_URL}/api/folders" || true
|
||||
done
|
||||
|
||||
# Import dashboards
|
||||
for dashboard_id in "${!DASHBOARDS[@]}"; do
|
||||
folder="${DASHBOARDS[$dashboard_id]}"
|
||||
echo "Importing dashboard ${dashboard_id} to folder ${folder}..."
|
||||
|
||||
# Get folder UID
|
||||
folder_uid=$(curl -s -u "${GRAFANA_USER}:${GRAFANA_PASSWORD}" \
|
||||
"${GRAFANA_URL}/api/folders" | \
|
||||
jq -r ".[] | select(.title==\"${folder}\") | .uid")
|
||||
|
||||
# Download and import dashboard
|
||||
dashboard_json=$(curl -s "https://grafana.com/api/dashboards/${dashboard_id}/revisions/1/download")
|
||||
|
||||
# Prepare import payload
|
||||
import_payload=$(jq -n \
|
||||
--arg folderUid "$folder_uid" \
|
||||
--argjson dashboard "$dashboard_json" \
|
||||
'{
|
||||
dashboard: $dashboard,
|
||||
folderUid: $folderUid,
|
||||
overwrite: true
|
||||
}')
|
||||
|
||||
curl -s -X POST -H "Content-Type: application/json" \
|
||||
-u "${GRAFANA_USER}:${GRAFANA_PASSWORD}" \
|
||||
-d "$import_payload" \
|
||||
"${GRAFANA_URL}/api/dashboards/db" | jq -r '.status'
|
||||
done
|
||||
|
||||
echo "Stopping port-forward..."
|
||||
kill $PF_PID
|
||||
|
||||
echo "Dashboard import complete!"
|
||||
echo "Access Grafana at: https://grafana.kube.huskypup.net"
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# Import Unifi dashboards into Grafana via API
|
||||
# This script downloads the dashboards from GitHub and imports them properly
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${GREEN}=== Importing Unifi Dashboards to Grafana ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Port forward to Grafana
|
||||
echo "Setting up port-forward to Grafana..."
|
||||
kubectl -n grafana port-forward svc/grafana 3000:80 >/dev/null 2>&1 &
|
||||
PF_PID=$!
|
||||
sleep 3
|
||||
|
||||
# Grafana API credentials (using default admin/admin for now)
|
||||
GRAFANA_URL="http://localhost:3000"
|
||||
API_KEY=""
|
||||
|
||||
# Function to cleanup
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "Cleaning up port-forward..."
|
||||
kill $PF_PID 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Get admin password from Kubernetes secret or use default
|
||||
ADMIN_PASS=$(kubectl -n grafana get secret grafana -o jsonpath='{.data.admin-password}' 2>/dev/null | base64 -d || echo "admin")
|
||||
|
||||
# Dashboards to import
|
||||
DASHBOARDS=("unifi-access-points" "unifi-clients" "unifi-gateway" "unifi-pdu" "unifi-sites" "unifi-switches")
|
||||
|
||||
for dashboard in "${DASHBOARDS[@]}"; do
|
||||
echo -e "${YELLOW}Importing ${dashboard}...${NC}"
|
||||
|
||||
# Download dashboard
|
||||
curl -sL "https://raw.githubusercontent.com/timothystewart6/unpoller-unifi/main/grafana/provisioning/dashboards/${dashboard}.json" -o "/tmp/${dashboard}.json"
|
||||
|
||||
# Fix datasource references and wrap for import
|
||||
python3 << PYEOF
|
||||
import json
|
||||
|
||||
with open('/tmp/${dashboard}.json', 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Remove template fields
|
||||
data.pop('__inputs', None)
|
||||
data.pop('__requires', None)
|
||||
data.pop('id', None)
|
||||
|
||||
# Fix datasource references
|
||||
def fix_datasource(obj):
|
||||
if isinstance(obj, dict):
|
||||
for key, value in obj.items():
|
||||
if key == 'datasource':
|
||||
if isinstance(value, str) and ('DS_PROMETHEUS' in value or value == ''):
|
||||
obj[key] = {'type': 'prometheus', 'uid': 'Prometheus'}
|
||||
elif isinstance(value, dict) and value.get('type') == 'prometheus':
|
||||
obj[key] = {'type': 'prometheus', 'uid': 'Prometheus'}
|
||||
else:
|
||||
fix_datasource(value)
|
||||
elif isinstance(obj, list):
|
||||
for item in obj:
|
||||
fix_datasource(item)
|
||||
|
||||
fix_datasource(data)
|
||||
|
||||
# Wrap for import API
|
||||
import_data = {
|
||||
"dashboard": data,
|
||||
"overwrite": True,
|
||||
"inputs": [],
|
||||
"folderId": 0
|
||||
}
|
||||
|
||||
with open('/tmp/${dashboard}-import.json', 'w') as f:
|
||||
json.dump(import_data, f)
|
||||
PYEOF
|
||||
|
||||
# Import via API
|
||||
response=$(curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "admin:${ADMIN_PASS}" \
|
||||
-d @/tmp/${dashboard}-import.json \
|
||||
"${GRAFANA_URL}/api/dashboards/db")
|
||||
|
||||
if echo "$response" | grep -q '"status":"success"'; then
|
||||
echo -e "${GREEN}✓ Successfully imported ${dashboard}${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Failed to import ${dashboard}: $response${NC}"
|
||||
fi
|
||||
|
||||
rm -f "/tmp/${dashboard}.json" "/tmp/${dashboard}-import.json"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Dashboard import complete!${NC}"
|
||||
echo ""
|
||||
echo "Visit https://grafana.kube.huskypup.net and search for 'Unifi' to find your dashboards."
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
# https://longhorn.io/docs/1.6.2/deploy/important-notes/#pod-security-policies-disabled--pod-security-admission-introduction
|
||||
cat <<EOF | kubectl apply -f -
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: metallb-system
|
||||
labels:
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
pod-security.kubernetes.io/enforce-version: latest
|
||||
pod-security.kubernetes.io/audit: privileged
|
||||
pod-security.kubernetes.io/audit-version: latest
|
||||
pod-security.kubernetes.io/warn: privileged
|
||||
pod-security.kubernetes.io/warn-version: latest
|
||||
EOF
|
||||
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/bin/bash
|
||||
# Nessus Bootstrap Script - Fully Automated
|
||||
# This script runs as a presync hook to set up Nessus dependencies
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=================================================================="
|
||||
echo "Nessus Presync - Fully Automated Bootstrap"
|
||||
echo "=================================================================="
|
||||
echo "This will set up:"
|
||||
echo " ✓ PostgreSQL database (CloudNativePG) with low resources"
|
||||
echo " ✓ Auto-generated database password (via ESO)"
|
||||
echo " ✓ Admin credentials from Vault"
|
||||
echo " ✓ Persistent storage for scan data"
|
||||
echo " ✓ OAuth2-Proxy authentication (Authentik SSO)"
|
||||
echo ""
|
||||
echo "Expected time: 3-5 minutes"
|
||||
echo "=================================================================="
|
||||
echo ""
|
||||
|
||||
NS=nessus
|
||||
CLUSTER=pg-nessus
|
||||
|
||||
# Get the script directory and move to repo root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${REPO_ROOT}/apps" || exit 1
|
||||
|
||||
# 1) Ensure namespace exists
|
||||
echo "⚙️ Creating namespace ${NS}..."
|
||||
kubectl get ns "${NS}" >/dev/null 2>&1 || kubectl create ns "${NS}"
|
||||
|
||||
# 2) Apply CNPG cluster (auto-generates DB password)
|
||||
echo "⚙️ Deploying PostgreSQL cluster (low resources)..."
|
||||
kubectl apply -f nessus/cnpg-cluster.yaml
|
||||
|
||||
# 3) Wait for CNPG cluster to be ready
|
||||
echo "⏳ Waiting for PostgreSQL cluster to be ready..."
|
||||
kubectl -n "${NS}" wait --for=condition=Ready "cluster/${CLUSTER}" --timeout=300s || {
|
||||
echo "⚠️ WARNING: PostgreSQL cluster not ready yet"
|
||||
echo " This is normal on first deployment - rerun 'helmfile apply' in a few minutes"
|
||||
exit 0
|
||||
}
|
||||
echo "✅ PostgreSQL cluster ready"
|
||||
echo ""
|
||||
|
||||
# 4) Apply ESO secrets (auto-rotate DB passwords)
|
||||
echo "⚙️ Configuring auto-rotating database passwords..."
|
||||
kubectl apply -f nessus/cnpg-secrets.yaml
|
||||
|
||||
# Wait for DB secret to sync
|
||||
echo "⏳ Waiting for database secret to sync..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n "${NS}" get secret nessus-db-secret >/dev/null 2>&1; then
|
||||
echo "✅ Database secret synced"
|
||||
break
|
||||
fi
|
||||
echo " waiting... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 5) Apply Nessus admin credentials ExternalSecret
|
||||
echo "⚙️ Syncing admin credentials from Vault..."
|
||||
kubectl apply -f nessus/external-secret.yaml
|
||||
|
||||
# Wait for admin secret to sync
|
||||
echo "⏳ Waiting for admin credentials to sync from Vault..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n "${NS}" get secret nessus-admin-credentials >/dev/null 2>&1; then
|
||||
echo "✅ Admin credentials synced from Vault"
|
||||
break
|
||||
fi
|
||||
echo " waiting... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Check if credentials exist in Vault
|
||||
if ! kubectl -n "${NS}" get secret nessus-admin-credentials >/dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "⚠️ WARNING: Nessus admin credentials not found in Vault!"
|
||||
echo ""
|
||||
echo "BOOTSTRAP REQUIRED - Run this command first:"
|
||||
echo "=================================================================="
|
||||
echo "kubectl exec -n vault vault-0 -- vault kv put secret/nessus \\"
|
||||
echo " admin-username=\"admin\" \\"
|
||||
echo " admin-password=\"YourSecurePassword123!\""
|
||||
echo "=================================================================="
|
||||
echo ""
|
||||
echo "After storing credentials, rerun: helmfile apply"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 6) Apply PVC for Nessus scan data
|
||||
echo "⚙️ Creating persistent storage for scan data..."
|
||||
kubectl apply -f nessus/pvc.yaml
|
||||
|
||||
# 7) Apply Nessus deployment
|
||||
echo "⚙️ Deploying Nessus scanner..."
|
||||
kubectl apply -f nessus/deployment.yaml
|
||||
|
||||
# 8) Apply Nessus ingress
|
||||
echo "⚙️ Configuring ingress with OAuth2-Proxy..."
|
||||
kubectl apply -f nessus/ingress.yaml
|
||||
|
||||
echo ""
|
||||
echo "✅ Nessus presync complete - fully automated!"
|
||||
echo ""
|
||||
echo "=================================================================="
|
||||
echo "IMPORTANT: First-time setup instructions"
|
||||
echo "=================================================================="
|
||||
echo ""
|
||||
echo "If you haven't already, store Nessus credentials in Vault:"
|
||||
echo ""
|
||||
echo "kubectl exec -n vault vault-0 -- vault kv put secret/nessus \\"
|
||||
echo " admin-username=\"admin\" \\"
|
||||
echo " admin-password=\"YourSecurePassword123!\""
|
||||
echo ""
|
||||
echo "Then rerun: helmfile apply"
|
||||
echo "=================================================================="
|
||||
echo ""
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
# iperf3 server on node-41
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: iperf3-server
|
||||
namespace: default
|
||||
labels:
|
||||
app: iperf3-server
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-node-41
|
||||
containers:
|
||||
- name: iperf3
|
||||
image: networkstatic/iperf3:latest
|
||||
command: ["iperf3"]
|
||||
args: ["-s"]
|
||||
ports:
|
||||
- containerPort: 5201
|
||||
protocol: TCP
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
---
|
||||
# iperf3 server service
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: iperf3-server
|
||||
namespace: default
|
||||
spec:
|
||||
selector:
|
||||
app: iperf3-server
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5201
|
||||
targetPort: 5201
|
||||
type: ClusterIP
|
||||
---
|
||||
# iperf3 client on node-42
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: iperf3-client-node42
|
||||
namespace: default
|
||||
labels:
|
||||
app: iperf3-client
|
||||
test: node42
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-node-42
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: iperf3
|
||||
image: networkstatic/iperf3:latest
|
||||
command: ["/bin/sh"]
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for iperf3 server to be ready..."
|
||||
sleep 5
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Network Speed Test: node-42 → node-41"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Test 1: TCP Bandwidth (10 seconds)"
|
||||
iperf3 -c iperf3-server -t 10 -P 4
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Test 2: TCP Bandwidth with larger window (10 seconds)"
|
||||
iperf3 -c iperf3-server -t 10 -P 4 -w 256K
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test Complete!"
|
||||
echo "=========================================="
|
||||
sleep 60
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
---
|
||||
# iperf3 client on node-43
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: iperf3-client-node43
|
||||
namespace: default
|
||||
labels:
|
||||
app: iperf3-client
|
||||
test: node43
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-node-43
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: iperf3
|
||||
image: networkstatic/iperf3:latest
|
||||
command: ["/bin/sh"]
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for iperf3 server to be ready..."
|
||||
sleep 5
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Network Speed Test: node-43 → node-41"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Test 1: TCP Bandwidth (10 seconds)"
|
||||
iperf3 -c iperf3-server -t 10 -P 4
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Test 2: TCP Bandwidth with larger window (10 seconds)"
|
||||
iperf3 -c iperf3-server -t 10 -P 4 -w 256K
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test Complete!"
|
||||
echo "=========================================="
|
||||
sleep 60
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
---
|
||||
# iperf3 client on node-44
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: iperf3-client-node44
|
||||
namespace: default
|
||||
labels:
|
||||
app: iperf3-client
|
||||
test: node44
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-node-44
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: iperf3
|
||||
image: networkstatic/iperf3:latest
|
||||
command: ["/bin/sh"]
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for iperf3 server to be ready..."
|
||||
sleep 5
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Network Speed Test: node-44 → node-41"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Test 1: TCP Bandwidth (10 seconds)"
|
||||
iperf3 -c iperf3-server -t 10 -P 4
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Test 2: TCP Bandwidth with larger window (10 seconds)"
|
||||
iperf3 -c iperf3-server -t 10 -P 4 -w 256K
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test Complete!"
|
||||
echo "=========================================="
|
||||
sleep 60
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
Executable
+231
@@ -0,0 +1,231 @@
|
||||
#!/bin/bash
|
||||
# Rook-Ceph Preparation Script
|
||||
# Wipes NVMe drives on all nodes, creates namespace with proper PSS labels,
|
||||
# cleans up stale resources, and verifies node availability
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NS="rook-ceph"
|
||||
WIPE_IMAGE="quay.io/ceph/ceph:v19.2.0"
|
||||
|
||||
# Node to NVMe device mapping
|
||||
declare -A NODE_NVME=(
|
||||
["talos-cp-01"]="nvme0n1"
|
||||
["talos-cp-02"]="nvme1n1"
|
||||
["talos-cp-03"]="nvme0n1"
|
||||
["talos-cp-04"]="nvme0n1"
|
||||
)
|
||||
|
||||
echo "=== Rook-Ceph Preparation ==="
|
||||
|
||||
# Create namespace if it doesn't exist
|
||||
if ! kubectl get ns "${NS}" >/dev/null 2>&1; then
|
||||
echo "Creating namespace ${NS}..."
|
||||
kubectl create ns "${NS}"
|
||||
fi
|
||||
|
||||
# Apply privileged Pod Security Standards (required for Ceph)
|
||||
echo "Applying privileged Pod Security Standards..."
|
||||
kubectl label namespace "${NS}" pod-security.kubernetes.io/enforce=privileged --overwrite
|
||||
kubectl label namespace "${NS}" pod-security.kubernetes.io/audit=privileged --overwrite
|
||||
kubectl label namespace "${NS}" pod-security.kubernetes.io/warn=privileged --overwrite
|
||||
|
||||
# Clean up any stale OSD deployments
|
||||
echo "Cleaning up stale OSD resources..."
|
||||
for osd_deploy in $(kubectl -n "${NS}" get deployments -o name 2>/dev/null | grep rook-ceph-osd- || true); do
|
||||
echo " Deleting ${osd_deploy}..."
|
||||
kubectl -n "${NS}" delete "${osd_deploy}" --ignore-not-found=true
|
||||
done
|
||||
|
||||
# Clean up stale OSD prepare pods
|
||||
kubectl -n "${NS}" delete pods -l app=rook-ceph-osd-prepare --ignore-not-found=true 2>/dev/null || true
|
||||
|
||||
# Clean up any stale finalizers from previous failed deployments
|
||||
echo "Checking for stale CephCluster..."
|
||||
if kubectl -n "${NS}" get cephcluster rook-ceph >/dev/null 2>&1; then
|
||||
PHASE=$(kubectl -n "${NS}" get cephcluster rook-ceph -o jsonpath='{.status.phase}' 2>/dev/null || echo "Unknown")
|
||||
if [ "$PHASE" = "Deleting" ]; then
|
||||
echo "Found stale CephCluster in Deleting state, cleaning up finalizers..."
|
||||
kubectl -n "${NS}" patch cephcluster rook-ceph --type merge -p '{"metadata":{"finalizers":[]}}' 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Function to wipe NVMe on a specific node
|
||||
wipe_nvme() {
|
||||
local node=$1
|
||||
local device=$2
|
||||
local pod_name="wipe-nvme-${node}"
|
||||
|
||||
echo "Wiping /dev/${device} on ${node}..."
|
||||
|
||||
# Delete any existing wipe pod
|
||||
kubectl -n "${NS}" delete pod "${pod_name}" --ignore-not-found=true 2>/dev/null || true
|
||||
|
||||
# Create wipe pod
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: ${pod_name}
|
||||
namespace: ${NS}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
nodeName: ${node}
|
||||
tolerations:
|
||||
- operator: Exists
|
||||
containers:
|
||||
- name: wipe
|
||||
image: ${WIPE_IMAGE}
|
||||
securityContext:
|
||||
privileged: true
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -ex
|
||||
DEVICE="/dev/${device}"
|
||||
|
||||
# Skip if device doesn't exist
|
||||
if [ ! -b "\${DEVICE}" ]; then
|
||||
echo "Device \${DEVICE} not found, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "=== Wiping \${DEVICE} on ${node} ==="
|
||||
|
||||
# Remove any Ceph LVM volumes
|
||||
for vg in \$(vgs --noheadings -o vg_name 2>/dev/null | grep -i ceph || true); do
|
||||
echo "Removing VG: \${vg}"
|
||||
vgremove -ff "\${vg}" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Remove PV if exists
|
||||
pvremove -ff "\${DEVICE}" 2>/dev/null || true
|
||||
|
||||
# Remove device mapper entries
|
||||
dmsetup remove_all -f 2>/dev/null || true
|
||||
|
||||
# Wipe filesystem signatures
|
||||
wipefs -af "\${DEVICE}"
|
||||
|
||||
# Zap GPT/MBR
|
||||
sgdisk --zap-all "\${DEVICE}"
|
||||
|
||||
# Zero first 100MB (clears any remaining metadata)
|
||||
dd if=/dev/zero of="\${DEVICE}" bs=1M count=100 conv=fsync
|
||||
|
||||
# Zero last 100MB (clears backup GPT)
|
||||
SECTORS=\$(blockdev --getsz "\${DEVICE}")
|
||||
dd if=/dev/zero of="\${DEVICE}" bs=1M count=100 seek=\$((SECTORS/2048 - 100)) conv=fsync
|
||||
|
||||
echo "=== Wipe complete for \${DEVICE} ==="
|
||||
lsblk -f "\${DEVICE}"
|
||||
blkid "\${DEVICE}" || echo "No signatures (clean)"
|
||||
volumeMounts:
|
||||
- name: dev
|
||||
mountPath: /dev
|
||||
volumes:
|
||||
- name: dev
|
||||
hostPath:
|
||||
path: /dev
|
||||
EOF
|
||||
|
||||
# Wait for pod to complete
|
||||
echo " Waiting for wipe to complete on ${node}..."
|
||||
if ! kubectl -n "${NS}" wait --for=condition=Ready pod/"${pod_name}" --timeout=30s 2>/dev/null; then
|
||||
# Pod might have completed already
|
||||
true
|
||||
fi
|
||||
|
||||
# Wait for completion (up to 2 minutes)
|
||||
local timeout=120
|
||||
local elapsed=0
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
local phase=$(kubectl -n "${NS}" get pod "${pod_name}" -o jsonpath='{.status.phase}' 2>/dev/null || echo "Unknown")
|
||||
if [ "$phase" = "Succeeded" ]; then
|
||||
echo " ✓ Wipe completed on ${node}"
|
||||
kubectl -n "${NS}" logs "${pod_name}" 2>/dev/null | tail -5
|
||||
kubectl -n "${NS}" delete pod "${pod_name}" --ignore-not-found=true
|
||||
return 0
|
||||
elif [ "$phase" = "Failed" ]; then
|
||||
echo " ✗ Wipe failed on ${node}"
|
||||
kubectl -n "${NS}" logs "${pod_name}" 2>/dev/null | tail -20
|
||||
kubectl -n "${NS}" delete pod "${pod_name}" --ignore-not-found=true
|
||||
return 1
|
||||
fi
|
||||
sleep 5
|
||||
elapsed=$((elapsed + 5))
|
||||
done
|
||||
|
||||
echo " ✗ Wipe timed out on ${node}"
|
||||
kubectl -n "${NS}" delete pod "${pod_name}" --ignore-not-found=true
|
||||
return 1
|
||||
}
|
||||
|
||||
# Verify nodes are available
|
||||
echo "Verifying node availability..."
|
||||
NODE_COUNT=$(kubectl get nodes --no-headers 2>/dev/null | wc -l)
|
||||
if [ "${NODE_COUNT}" -lt 1 ]; then
|
||||
echo "ERROR: No nodes available in cluster"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found ${NODE_COUNT} node(s) available"
|
||||
|
||||
# Check for Ready nodes
|
||||
READY_NODES=$(kubectl get nodes --no-headers 2>/dev/null | grep -c " Ready" || echo "0")
|
||||
if [ "${READY_NODES}" -lt 1 ]; then
|
||||
echo "ERROR: No Ready nodes available in cluster"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found ${READY_NODES} Ready node(s)"
|
||||
|
||||
# Check if Ceph OSDs already exist (skip wipe if so, unless forced)
|
||||
FORCE_WIPE="${FORCE_WIPE:-false}"
|
||||
if [ "$FORCE_WIPE" != "true" ]; then
|
||||
OSD_COUNT=$(kubectl -n "${NS}" get pods -l app=rook-ceph-osd --no-headers 2>/dev/null | grep -c Running || echo "0")
|
||||
if [ "$OSD_COUNT" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "WARNING: Found ${OSD_COUNT} running OSD(s). Skipping disk wipe to protect existing data."
|
||||
echo "To force wipe, run: FORCE_WIPE=true ./scripts/rook-ceph-prepare.sh"
|
||||
echo ""
|
||||
echo "=== Rook-Ceph preparation complete (no wipe) ==="
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Wipe NVMe drives on all configured nodes
|
||||
echo ""
|
||||
echo "=== Wiping NVMe drives ==="
|
||||
WIPE_FAILED=0
|
||||
for node in "${!NODE_NVME[@]}"; do
|
||||
device="${NODE_NVME[$node]}"
|
||||
|
||||
# Check if node exists in cluster
|
||||
if ! kubectl get node "${node}" >/dev/null 2>&1; then
|
||||
echo "Node ${node} not found in cluster, skipping..."
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! wipe_nvme "${node}" "${device}"; then
|
||||
echo "WARNING: Failed to wipe ${device} on ${node}"
|
||||
WIPE_FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $WIPE_FAILED -eq 1 ]; then
|
||||
echo ""
|
||||
echo "WARNING: Some wipe operations failed. Check logs above."
|
||||
echo "Continuing with Ceph setup anyway..."
|
||||
fi
|
||||
|
||||
# List nodes for informational purposes
|
||||
echo ""
|
||||
echo "Cluster nodes:"
|
||||
kubectl get nodes -o wide --no-headers 2>/dev/null | while read -r line; do
|
||||
echo " $line"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Rook-Ceph preparation complete ==="
|
||||
echo "NVMe drives have been wiped and namespace is ready."
|
||||
echo "Run 'helmfile apply' to deploy Ceph."
|
||||
Executable
+184
@@ -0,0 +1,184 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Starting Network Benchmark Between Nodes..."
|
||||
echo ""
|
||||
|
||||
# Deploy iperf3 server on node-41
|
||||
echo "Deploying iperf3 server on node-41..."
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: iperf3-server
|
||||
namespace: default
|
||||
labels:
|
||||
app: iperf3-server
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-node-41
|
||||
containers:
|
||||
- name: iperf3
|
||||
image: networkstatic/iperf3:latest
|
||||
command: ["iperf3"]
|
||||
args: ["-s"]
|
||||
ports:
|
||||
- containerPort: 5201
|
||||
protocol: TCP
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: iperf3-server
|
||||
namespace: default
|
||||
spec:
|
||||
selector:
|
||||
app: iperf3-server
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5201
|
||||
targetPort: 5201
|
||||
type: ClusterIP
|
||||
EOF
|
||||
|
||||
echo "Waiting for server to be ready..."
|
||||
kubectl wait --for=condition=ready pod/iperf3-server -n default --timeout=60s
|
||||
sleep 2
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test 1: node-42 → node-41"
|
||||
echo "=========================================="
|
||||
kubectl run iperf3-client-42 \
|
||||
--image=networkstatic/iperf3:latest \
|
||||
--restart=Never \
|
||||
--rm -i \
|
||||
--overrides='
|
||||
{
|
||||
"spec": {
|
||||
"nodeSelector": {
|
||||
"kubernetes.io/hostname": "talos-node-42"
|
||||
},
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 1000,
|
||||
"seccompProfile": {
|
||||
"type": "RuntimeDefault"
|
||||
}
|
||||
},
|
||||
"containers": [{
|
||||
"name": "iperf3-client-42",
|
||||
"image": "networkstatic/iperf3:latest",
|
||||
"stdin": true,
|
||||
"tty": true,
|
||||
"command": ["iperf3", "-c", "iperf3-server", "-t", "10", "-P", "4"],
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"capabilities": {
|
||||
"drop": ["ALL"]
|
||||
},
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 1000
|
||||
}
|
||||
}]
|
||||
}
|
||||
}' 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test 2: node-43 → node-41"
|
||||
echo "=========================================="
|
||||
kubectl run iperf3-client-43 \
|
||||
--image=networkstatic/iperf3:latest \
|
||||
--restart=Never \
|
||||
--rm -i \
|
||||
--overrides='
|
||||
{
|
||||
"spec": {
|
||||
"nodeSelector": {
|
||||
"kubernetes.io/hostname": "talos-node-43"
|
||||
},
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 1000,
|
||||
"seccompProfile": {
|
||||
"type": "RuntimeDefault"
|
||||
}
|
||||
},
|
||||
"containers": [{
|
||||
"name": "iperf3-client-43",
|
||||
"image": "networkstatic/iperf3:latest",
|
||||
"stdin": true,
|
||||
"tty": true,
|
||||
"command": ["iperf3", "-c", "iperf3-server", "-t", "10", "-P", "4"],
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"capabilities": {
|
||||
"drop": ["ALL"]
|
||||
},
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 1000
|
||||
}
|
||||
}]
|
||||
}
|
||||
}' 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test 3: node-44 → node-41"
|
||||
echo "=========================================="
|
||||
kubectl run iperf3-client-44 \
|
||||
--image=networkstatic/iperf3:latest \
|
||||
--restart=Never \
|
||||
--rm -i \
|
||||
--overrides='
|
||||
{
|
||||
"spec": {
|
||||
"nodeSelector": {
|
||||
"kubernetes.io/hostname": "talos-node-44"
|
||||
},
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 1000,
|
||||
"seccompProfile": {
|
||||
"type": "RuntimeDefault"
|
||||
}
|
||||
},
|
||||
"containers": [{
|
||||
"name": "iperf3-client-44",
|
||||
"image": "networkstatic/iperf3:latest",
|
||||
"stdin": true,
|
||||
"tty": true,
|
||||
"command": ["iperf3", "-c", "iperf3-server", "-t", "10", "-P", "4"],
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"capabilities": {
|
||||
"drop": ["ALL"]
|
||||
},
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 1000
|
||||
}
|
||||
}]
|
||||
}
|
||||
}' 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Cleaning up..."
|
||||
kubectl delete pod iperf3-server -n default
|
||||
kubectl delete service iperf3-server -n default
|
||||
|
||||
echo ""
|
||||
echo "Network benchmark complete!"
|
||||
Executable
+126
@@ -0,0 +1,126 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# setup-ceph-saml.sh
|
||||
# Configures Ceph Dashboard SAML2 SSO with Authentik
|
||||
# This script should be run after both Ceph and Authentik are deployed
|
||||
|
||||
CEPH_NAMESPACE="rook-ceph"
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
CEPH_BASE_URL="https://ceph.kube.huskypup.net"
|
||||
AUTHENTIK_SAML_METADATA="https://auth.kube.huskypup.net/application/saml/ceph-dashboard/metadata/"
|
||||
USERNAME_ATTRIBUTE="username"
|
||||
|
||||
echo "=== Setting up Ceph Dashboard SAML2 SSO ==="
|
||||
|
||||
# Check if Ceph tools pod is available
|
||||
echo "Checking for Ceph tools pod..."
|
||||
if ! kubectl -n "${CEPH_NAMESPACE}" get deploy rook-ceph-tools >/dev/null 2>&1; then
|
||||
echo "ERROR: rook-ceph-tools deployment not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for Ceph tools to be ready
|
||||
echo "Waiting for Ceph tools pod to be ready..."
|
||||
kubectl -n "${CEPH_NAMESPACE}" wait --for=condition=Available deployment/rook-ceph-tools --timeout=120s || {
|
||||
echo "WARNING: Ceph tools pod may not be ready. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Check Ceph cluster health
|
||||
echo "Checking Ceph cluster health..."
|
||||
CEPH_HEALTH=$(kubectl -n "${CEPH_NAMESPACE}" exec deploy/rook-ceph-tools -- ceph health 2>/dev/null || echo "UNKNOWN")
|
||||
echo "Ceph health: ${CEPH_HEALTH}"
|
||||
|
||||
if [[ "$CEPH_HEALTH" == "UNKNOWN" ]]; then
|
||||
echo "ERROR: Cannot communicate with Ceph cluster"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Authentik SAML metadata is accessible
|
||||
echo "Verifying Authentik SAML metadata endpoint..."
|
||||
HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" "${AUTHENTIK_SAML_METADATA}" 2>/dev/null || echo "000")
|
||||
if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "302" ]]; then
|
||||
echo "WARNING: Authentik SAML metadata endpoint returned HTTP ${HTTP_CODE}"
|
||||
echo "The Ceph SAML application may not be configured in Authentik yet."
|
||||
echo "Ensure the ceph-blueprint.yaml is applied to Authentik."
|
||||
fi
|
||||
|
||||
# Configure SAML2 SSO
|
||||
echo "Configuring SAML2 SSO..."
|
||||
SAML_CONFIG=$(kubectl -n "${CEPH_NAMESPACE}" exec deploy/rook-ceph-tools -- \
|
||||
ceph dashboard sso setup saml2 \
|
||||
"${CEPH_BASE_URL}" \
|
||||
"${AUTHENTIK_SAML_METADATA}" \
|
||||
"${USERNAME_ATTRIBUTE}" 2>&1) || {
|
||||
echo "ERROR: Failed to configure SAML2 SSO"
|
||||
echo "$SAML_CONFIG"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "SAML2 configuration applied successfully"
|
||||
|
||||
# Enable SSO
|
||||
echo "Enabling SAML2 SSO..."
|
||||
kubectl -n "${CEPH_NAMESPACE}" exec deploy/rook-ceph-tools -- \
|
||||
ceph dashboard sso enable saml2
|
||||
|
||||
# Verify SSO status
|
||||
SSO_STATUS=$(kubectl -n "${CEPH_NAMESPACE}" exec deploy/rook-ceph-tools -- \
|
||||
ceph dashboard sso status 2>/dev/null)
|
||||
echo "SSO Status: ${SSO_STATUS}"
|
||||
|
||||
# Create SSO users from Authentik
|
||||
echo ""
|
||||
echo "Creating SSO users in Ceph Dashboard..."
|
||||
|
||||
# Get active users from Authentik database
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$CNPG_POD" ]; then
|
||||
# Get password from secret
|
||||
DB_PASSWORD=$(kubectl -n "${AUTHENTIK_NAMESPACE}" get secret pg-authentik-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Query for active non-service users
|
||||
USERS=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -c postgres -- \
|
||||
sh -c "PGPASSWORD='${DB_PASSWORD}' psql -h localhost -U app -d app -t -c \"SELECT username FROM authentik_core_user WHERE is_active = true AND username NOT LIKE 'ak-%' AND username != 'AnonymousUser';\"" 2>/dev/null | tr -d ' ' | grep -v '^$' || echo "")
|
||||
|
||||
if [ -n "$USERS" ]; then
|
||||
echo "Found users in Authentik: $(echo $USERS | tr '\n' ' ')"
|
||||
|
||||
# Get existing Ceph users
|
||||
EXISTING_USERS=$(kubectl -n "${CEPH_NAMESPACE}" exec deploy/rook-ceph-tools -- \
|
||||
ceph dashboard ac-user-show 2>/dev/null | tr -d '[]"' | tr ',' '\n' | tr -d ' ')
|
||||
|
||||
for USER in $USERS; do
|
||||
if echo "$EXISTING_USERS" | grep -q "^${USER}$"; then
|
||||
echo " User '${USER}' already exists in Ceph"
|
||||
else
|
||||
echo " Creating user '${USER}' with administrator role..."
|
||||
# Create user with a temporary password (SSO will bypass password auth)
|
||||
echo "sso-managed-password-$(date +%s)" | kubectl -n "${CEPH_NAMESPACE}" exec -i deploy/rook-ceph-tools -- \
|
||||
ceph dashboard ac-user-create "${USER}" -i - administrator 2>/dev/null && \
|
||||
echo " Created user '${USER}'" || \
|
||||
echo " WARNING: Failed to create user '${USER}'"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No users found in Authentik to sync"
|
||||
fi
|
||||
else
|
||||
echo "WARNING: Could not find Authentik PostgreSQL pod. Skipping user sync."
|
||||
echo "You may need to manually create Ceph users matching your Authentik usernames."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Ceph Dashboard SAML2 SSO Setup Complete ==="
|
||||
echo ""
|
||||
echo "Configuration:"
|
||||
echo " Base URL: ${CEPH_BASE_URL}"
|
||||
echo " IdP Metadata: ${AUTHENTIK_SAML_METADATA}"
|
||||
echo " Username Attribute: ${USERNAME_ATTRIBUTE}"
|
||||
echo ""
|
||||
echo "To login:"
|
||||
echo " 1. Navigate to ${CEPH_BASE_URL}/auth/saml2/login"
|
||||
echo " 2. Authenticate with Authentik"
|
||||
echo " 3. You will be redirected to Ceph Dashboard"
|
||||
echo ""
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# setup-netbird-api-token.sh - Bootstrap Netbird API token for operator + exporter
|
||||
#
|
||||
# Creates a service user in Netbird and generates a long-lived PAT (Personal Access Token)
|
||||
# for automation. Stores the token in Vault for ExternalSecret consumption.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/setup-netbird-api-token.sh <admin-pat>
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Netbird management server running and accessible
|
||||
# - Admin PAT from Netbird dashboard (Settings → Personal Access Tokens)
|
||||
# - Vault initialized and unsealed
|
||||
# - kubectl configured for the cluster
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ADMIN_PAT="${1:?Usage: $0 <admin-pat>}"
|
||||
NETBIRD_API="https://netbird.kube.huskypup.net"
|
||||
SERVICE_USER_NAME="k8s-operator"
|
||||
PAT_NAME="operator-automation"
|
||||
|
||||
echo "=== Netbird API Token Bootstrap ==="
|
||||
|
||||
# Check for existing service user
|
||||
echo "Checking for existing service user '${SERVICE_USER_NAME}'..."
|
||||
USERS=$(curl -sf -H "Authorization: Token ${ADMIN_PAT}" \
|
||||
"${NETBIRD_API}/api/users" 2>/dev/null || echo "[]")
|
||||
|
||||
SERVICE_USER_ID=$(echo "${USERS}" | jq -r \
|
||||
".[] | select(.name == \"${SERVICE_USER_NAME}\" and .is_service_user == true) | .id" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "${SERVICE_USER_ID}" ]; then
|
||||
echo "Creating service user '${SERVICE_USER_NAME}'..."
|
||||
RESPONSE=$(curl -sf -X POST \
|
||||
-H "Authorization: Token ${ADMIN_PAT}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\": \"${SERVICE_USER_NAME}\", \"role\": \"admin\", \"is_service_user\": true, \"auto_groups\": []}" \
|
||||
"${NETBIRD_API}/api/users")
|
||||
SERVICE_USER_ID=$(echo "${RESPONSE}" | jq -r '.id')
|
||||
echo " Created service user: ${SERVICE_USER_ID}"
|
||||
else
|
||||
echo " Service user already exists: ${SERVICE_USER_ID}"
|
||||
fi
|
||||
|
||||
# Create PAT for the service user
|
||||
echo "Creating Personal Access Token '${PAT_NAME}'..."
|
||||
# Set expiration to 365 days from now
|
||||
EXPIRES=$(date -u -d "+365 days" "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
||||
date -u -v+365d "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
|
||||
|
||||
PAT_RESPONSE=$(curl -sf -X POST \
|
||||
-H "Authorization: Token ${ADMIN_PAT}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\": \"${PAT_NAME}\", \"expires_in\": 365}" \
|
||||
"${NETBIRD_API}/api/users/${SERVICE_USER_ID}/tokens")
|
||||
|
||||
API_TOKEN=$(echo "${PAT_RESPONSE}" | jq -r '.plain_token')
|
||||
|
||||
if [ -z "${API_TOKEN}" ] || [ "${API_TOKEN}" = "null" ]; then
|
||||
echo "ERROR: Failed to create PAT. Response:"
|
||||
echo "${PAT_RESPONSE}" | jq . 2>/dev/null || echo "${PAT_RESPONSE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " PAT created successfully"
|
||||
|
||||
# Store in Vault
|
||||
echo "Storing API token in Vault at secret/netbird-api-token..."
|
||||
ROOT_TOKEN=$(kubectl -n vault get secret vault-init-keys -o jsonpath='{.data.VAULT_ROOT_TOKEN}' | base64 -d)
|
||||
kubectl exec -n vault vault-0 -- env "VAULT_TOKEN=${ROOT_TOKEN}" \
|
||||
vault kv put secret/netbird-api-token api-token="${API_TOKEN}"
|
||||
|
||||
echo ""
|
||||
echo "=== Netbird API Token Bootstrap Complete ==="
|
||||
echo " Service User: ${SERVICE_USER_NAME} (${SERVICE_USER_ID})"
|
||||
echo " Vault Path: secret/netbird-api-token"
|
||||
echo " Expires: ~365 days"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Run: helmfile apply (deploys operator + exporter with the token)"
|
||||
echo " 2. Annotate services: kubectl annotate svc <name> -n <ns> netbird.io/expose=true"
|
||||
Executable
+181
@@ -0,0 +1,181 @@
|
||||
#!/bin/bash
|
||||
# setup-unpoller-credentials.sh
|
||||
# Store Unifi credentials in Vault for Unpoller
|
||||
#
|
||||
# This script helps you store your Unifi controller credentials in Vault
|
||||
# so that Unpoller can authenticate and collect metrics.
|
||||
#
|
||||
# USAGE:
|
||||
# ======
|
||||
# 1. With username/password:
|
||||
# ./scripts/setup-unpoller-credentials.sh --user admin --pass yourpassword
|
||||
#
|
||||
# 2. With API key (same as External DNS):
|
||||
# ./scripts/setup-unpoller-credentials.sh --use-api-key
|
||||
#
|
||||
# 3. Interactive mode (prompts for credentials):
|
||||
# ./scripts/setup-unpoller-credentials.sh
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() { echo -e "${GREEN}ℹ${NC} $1"; }
|
||||
print_warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
||||
print_error() { echo -e "${RED}✗${NC} $1"; }
|
||||
print_success() { echo -e "${GREEN}✓${NC} $1"; }
|
||||
|
||||
# Parse command line arguments
|
||||
USE_API_KEY=false
|
||||
UNIFI_USER=""
|
||||
UNIFI_PASS=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--use-api-key)
|
||||
USE_API_KEY=true
|
||||
shift
|
||||
;;
|
||||
--user)
|
||||
UNIFI_USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pass)
|
||||
UNIFI_PASS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --use-api-key Use the same API key as External DNS"
|
||||
echo " --user USERNAME Unifi username (local admin account)"
|
||||
echo " --pass PASSWORD Unifi password"
|
||||
echo " --help, -h Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 --user admin --pass mypassword"
|
||||
echo " $0 --use-api-key"
|
||||
echo " $0 # Interactive mode"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "=================================================="
|
||||
echo " Unpoller Credentials Setup for Vault"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Check if Vault is ready
|
||||
print_info "Checking if Vault is ready..."
|
||||
if ! kubectl -n vault get pod vault-0 >/dev/null 2>&1; then
|
||||
print_error "Vault pod not found! Please deploy infrastructure first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! kubectl -n vault exec vault-0 -- vault status >/dev/null 2>&1; then
|
||||
print_error "Vault is not ready! Please check Vault status."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Vault is ready"
|
||||
echo ""
|
||||
|
||||
# Get credentials
|
||||
if [ "$USE_API_KEY" = true ]; then
|
||||
print_info "Using API key from External DNS configuration..."
|
||||
API_KEY=$(kubectl -n external-dns get secret external-dns-unifi-secret -o jsonpath='{.data.api-key}' | base64 -d)
|
||||
UNIFI_USER="$API_KEY"
|
||||
UNIFI_PASS="$API_KEY"
|
||||
print_success "API key retrieved: ${API_KEY:0:10}..."
|
||||
elif [ -z "$UNIFI_USER" ] || [ -z "$UNIFI_PASS" ]; then
|
||||
# Interactive mode
|
||||
print_info "No credentials provided, entering interactive mode..."
|
||||
echo ""
|
||||
echo "Choose authentication method:"
|
||||
echo " 1) Use API key (same as External DNS)"
|
||||
echo " 2) Use Unifi local admin username/password"
|
||||
echo ""
|
||||
read -rp "Enter choice [1-2]: " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
print_info "Retrieving API key from External DNS..."
|
||||
API_KEY=$(kubectl -n external-dns get secret external-dns-unifi-secret -o jsonpath='{.data.api-key}' | base64 -d)
|
||||
UNIFI_USER="$API_KEY"
|
||||
UNIFI_PASS="$API_KEY"
|
||||
print_success "API key retrieved: ${API_KEY:0:10}..."
|
||||
;;
|
||||
2)
|
||||
read -rp "Enter Unifi username: " UNIFI_USER
|
||||
read -rsp "Enter Unifi password: " UNIFI_PASS
|
||||
echo ""
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid choice"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Validate credentials are not empty
|
||||
if [ -z "$UNIFI_USER" ] || [ -z "$UNIFI_PASS" ]; then
|
||||
print_error "Credentials cannot be empty!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "Storing credentials in Vault..."
|
||||
|
||||
# Store credentials in Vault
|
||||
if kubectl -n vault exec vault-0 -- vault kv put secret/unpoller \
|
||||
unifi-user="$UNIFI_USER" \
|
||||
unifi-pass="$UNIFI_PASS" >/dev/null 2>&1; then
|
||||
print_success "Credentials stored in Vault at: secret/unpoller"
|
||||
else
|
||||
print_error "Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "Verifying credentials were stored correctly..."
|
||||
|
||||
# Verify the secret exists
|
||||
if kubectl -n vault exec vault-0 -- vault kv get secret/unpoller >/dev/null 2>&1; then
|
||||
print_success "Credentials verified in Vault"
|
||||
else
|
||||
print_error "Failed to verify credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Setup complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Deploy or update Unpoller: helmfile apply"
|
||||
echo " 2. Wait for Unpoller to sync credentials from Vault (via ExternalSecret)"
|
||||
echo " 3. Check Unpoller logs: kubectl -n unpoller logs -l app.kubernetes.io/name=unpoller"
|
||||
echo " 4. Verify metrics in Prometheus: http://prometheus.kube.huskypup.net"
|
||||
echo " 5. View dashboards in Grafana: http://grafana.kube.huskypup.net"
|
||||
echo ""
|
||||
echo "Grafana will have 7 new Unifi dashboards:"
|
||||
echo " - Unifi Access Points"
|
||||
echo " - Unifi Clients"
|
||||
echo " - Unifi DPI (Deep Packet Inspection)"
|
||||
echo " - Unifi Gateway"
|
||||
echo " - Unifi Sites"
|
||||
echo " - Unifi Switches"
|
||||
echo " - Unifi PDU"
|
||||
echo ""
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# sync-gitlab-oauth.sh
|
||||
# Retrieves ArgoCD OAuth provider credentials from Authentik and stores them in Vault
|
||||
# This script should be run after Authentik is deployed and the ArgoCD blueprint is applied
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/argocd-oauth"
|
||||
|
||||
echo "=== Syncing ArgoCD OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Check if Authentik is running
|
||||
# if ! kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --no-headers 2>/dev/null | grep -q Running; then
|
||||
# echo "ERROR: Authentik is not running. Please deploy Authentik first."
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
# Note: This uses the Authentik API via the management interface
|
||||
echo "Retrieving ArgoCD OAuth credentials from Authentik..."
|
||||
|
||||
# Method 1: Try to get credentials directly from Authentik database
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "ArgoCD"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "ArgoCD"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If Method 1 fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Query the database for ArgoCD provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -- psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id, client_secret FROM authentik_providers_oauth2_oauth2provider WHERE name='ArgoCD';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "ERROR: Failed to retrieve ArgoCD OAuth credentials from Authentik"
|
||||
echo ""
|
||||
echo "Manual steps required:"
|
||||
echo "1. Access Authentik admin panel at https://auth.kube.huskypup.net"
|
||||
echo "2. Navigate to: Applications > Providers > ArgoCD"
|
||||
echo "3. Copy the Client ID and Client Secret"
|
||||
echo "4. Store them in Vault manually with:"
|
||||
echo " vault kv put ${VAULT_SECRET_PATH} client-id=\"<CLIENT_ID>\" client-secret=\"<CLIENT_SECRET>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..." # Only show first 10 chars
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing ArgoCD OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored ArgoCD OAuth credentials in Vault"
|
||||
echo ""
|
||||
echo "The External Secret Operator will now sync these credentials to the argocd namespace."
|
||||
echo "You can verify with:"
|
||||
echo " kubectl get externalsecret -n argocd argocd-oauth"
|
||||
echo " kubectl get secret -n argocd argocd-oauth-secret"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== ArgoCD OAuth Sync Complete ==="
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
# Sync GitLab admin status from Authentik "authentik Admins" group
|
||||
# This script grants admin access to users who are members of "authentik Admins" in Authentik
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
NAMESPACE="gitlab"
|
||||
ADMIN_GROUP="authentik Admins"
|
||||
|
||||
echo "🔄 Syncing GitLab admin permissions from Authentik..."
|
||||
|
||||
# Get GitLab root password
|
||||
GITLAB_ROOT_PASSWORD=$(kubectl get secret -n gitlab gitlab-gitlab-initial-root-password -o jsonpath='{.data.password}' 2>/dev/null | base64 -d || echo "")
|
||||
|
||||
if [ -z "$GITLAB_ROOT_PASSWORD" ]; then
|
||||
echo "❌ GitLab root password not found"
|
||||
echo " Please login to GitLab UI first to complete initial setup"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get GitLab API endpoint
|
||||
GITLAB_URL="https://gitlab.kube.huskypup.net"
|
||||
|
||||
echo "📋 Getting list of users from Authentik '$ADMIN_GROUP' group..."
|
||||
|
||||
# Get Authentik database password
|
||||
PGPASSWORD=$(kubectl get secret -n authentik pg-authentik-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Query Authentik database for users in admin group
|
||||
ADMIN_USERS=$(kubectl exec -n authentik pg-authentik-1 -- env PGPASSWORD="$PGPASSWORD" psql -h pg-authentik-rw -U app -d app -t -c "
|
||||
SELECT DISTINCT u.email
|
||||
FROM authentik_core_user u
|
||||
JOIN authentik_core_user_groups ug ON u.id = ug.user_id
|
||||
JOIN authentik_core_group g ON ug.group_id = g.group_uuid
|
||||
WHERE g.name = '$ADMIN_GROUP' AND u.is_active = true;
|
||||
" 2>/dev/null | grep -v "Defaulted" | xargs)
|
||||
|
||||
if [ -z "$ADMIN_USERS" ]; then
|
||||
echo "⚠️ No users found in Authentik '$ADMIN_GROUP' group"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✓ Found admin users: $ADMIN_USERS"
|
||||
echo ""
|
||||
|
||||
# Create GitLab API token (using root account)
|
||||
echo "🔑 Creating GitLab API token..."
|
||||
|
||||
# Try to login and get session
|
||||
SESSION_COOKIE=$(curl -sk -c - "$GITLAB_URL/users/sign_in" | grep '_gitlab_session' | awk '{print $7}')
|
||||
|
||||
# Get CSRF token
|
||||
CSRF_TOKEN=$(curl -sk -b "_gitlab_session=$SESSION_COOKIE" "$GITLAB_URL/users/sign_in" | grep -o 'name="authenticity_token" value="[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
# Login as root
|
||||
LOGIN_RESPONSE=$(curl -sk -b "_gitlab_session=$SESSION_COOKIE" -c - \
|
||||
-X POST "$GITLAB_URL/users/sign_in" \
|
||||
-d "user[login]=root&user[password]=$GITLAB_ROOT_PASSWORD&authenticity_token=$CSRF_TOKEN")
|
||||
|
||||
echo "⚠️ Note: GitLab CE doesn't support automatic admin assignment via API"
|
||||
echo " Users must be manually promoted to admin in GitLab UI"
|
||||
echo ""
|
||||
echo "📝 To manually grant admin access:"
|
||||
echo " 1. Login to GitLab as root: $GITLAB_URL"
|
||||
echo " 2. Go to Admin Area > Users"
|
||||
echo " 3. Find and edit each user: $ADMIN_USERS"
|
||||
echo " 4. Check 'Admin' checkbox and save"
|
||||
echo ""
|
||||
echo "💡 Alternatively, run this from a GitLab Rails console:"
|
||||
for email in $ADMIN_USERS; do
|
||||
echo " User.find_by(email: '$email')&.update(admin: true)"
|
||||
done
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# sync-gitlab-oauth.sh
|
||||
# Retrieves GitLab OAuth provider credentials from Authentik and stores them in Vault
|
||||
# This script should be run after Authentik is deployed and the GitLab blueprint is applied
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/gitlab-oauth"
|
||||
|
||||
echo "=== Syncing GitLab OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Check if Authentik is running
|
||||
if ! kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --no-headers 2>/dev/null | grep -q Running; then
|
||||
echo "ERROR: Authentik is not running. Please deploy Authentik first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
# Note: This uses the Authentik API via the management interface
|
||||
echo "Retrieving GitLab OAuth credentials from Authentik..."
|
||||
|
||||
# Method 1: Try to get credentials directly from Authentik database
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "GitLab"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "GitLab"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If Method 1 fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Query the database for GitLab provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -- psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id, client_secret FROM authentik_providers_oauth2_oauth2provider WHERE name='GitLab';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "ERROR: Failed to retrieve GitLab OAuth credentials from Authentik"
|
||||
echo ""
|
||||
echo "Manual steps required:"
|
||||
echo "1. Access Authentik admin panel at https://auth.kube.huskypup.net"
|
||||
echo "2. Navigate to: Applications > Providers > GitLab"
|
||||
echo "3. Copy the Client ID and Client Secret"
|
||||
echo "4. Store them in Vault manually with:"
|
||||
echo " vault kv put ${VAULT_SECRET_PATH} client-id=\"<CLIENT_ID>\" client-secret=\"<CLIENT_SECRET>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..." # Only show first 10 chars
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing GitLab OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored GitLab OAuth credentials in Vault"
|
||||
echo ""
|
||||
echo "The External Secret Operator will now sync these credentials to the gitlab namespace."
|
||||
echo "You can verify with:"
|
||||
echo " kubectl get externalsecret -n gitlab gitlab-oidc"
|
||||
echo " kubectl get secret -n gitlab gitlab-oidc-secret"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== GitLab OAuth Sync Complete ==="
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# sync-grafana-oauth.sh
|
||||
# Retrieves Grafana OAuth provider credentials from Authentik and stores them in Vault
|
||||
# This script should be run after Authentik is deployed and the Grafana blueprint is applied
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/grafana-oauth"
|
||||
|
||||
echo "=== Syncing Grafana OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Check if Authentik is running
|
||||
if ! kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --no-headers 2>/dev/null | grep -q Running; then
|
||||
echo "ERROR: Authentik is not running. Please deploy Authentik first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
# Note: This uses the Authentik API via the management interface
|
||||
echo "Retrieving Grafana OAuth credentials from Authentik..."
|
||||
|
||||
# Method 1: Try to get credentials directly from Authentik database
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Grafana"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Grafana"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If Method 1 fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Query the database for Grafana provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -- psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id, client_secret FROM authentik_providers_oauth2_oauth2provider WHERE name='Grafana';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "ERROR: Failed to retrieve Grafana OAuth credentials from Authentik"
|
||||
echo ""
|
||||
echo "Manual steps required:"
|
||||
echo "1. Access Authentik admin panel at https://auth.kube.huskypup.net"
|
||||
echo "2. Navigate to: Applications > Providers > Grafana"
|
||||
echo "3. Copy the Client ID and Client Secret"
|
||||
echo "4. Store them in Vault manually with:"
|
||||
echo " vault kv put ${VAULT_SECRET_PATH} client-id=\"<CLIENT_ID>\" client-secret=\"<CLIENT_SECRET>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..." # Only show first 10 chars
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing Grafana OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored Grafana OAuth credentials in Vault"
|
||||
echo ""
|
||||
echo "The External Secret Operator will now sync these credentials to the grafana namespace."
|
||||
echo "You can verify with:"
|
||||
echo " kubectl get externalsecret -n grafana grafana-oauth"
|
||||
echo " kubectl get secret -n grafana grafana-authentik-oauth"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Grafana OAuth Sync Complete ==="
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# scripts/sync-guacamole-oauth.sh
|
||||
# Sync Guacamole OAuth credentials from Authentik to Vault
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/guacamole-oauth"
|
||||
|
||||
echo "=== Syncing Guacamole OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
echo "Retrieving Guacamole OAuth credentials from Authentik..."
|
||||
|
||||
# Try to get credentials via ak command
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "guacamole"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "guacamole"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If command fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get PostgreSQL password
|
||||
PG_PASSWORD=$(kubectl get secret -n "${AUTHENTIK_NAMESPACE}" pg-authentik-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Query the database for guacamole provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -c postgres -- env PGPASSWORD="${PG_PASSWORD}" psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id, client_secret FROM authentik_providers_oauth2_oauth2provider WHERE name='guacamole';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "ERROR: Failed to retrieve Guacamole OAuth credentials from Authentik"
|
||||
echo ""
|
||||
echo "Manual steps required:"
|
||||
echo "1. Access Authentik admin panel at https://auth.kube.huskypup.net"
|
||||
echo "2. Navigate to: Applications > Providers > guacamole"
|
||||
echo "3. Copy the Client ID and Client Secret"
|
||||
echo "4. Store them in Vault manually with:"
|
||||
echo " kubectl exec -n vault <vault-pod> -- vault kv put ${VAULT_SECRET_PATH} client-id=\"<CLIENT_ID>\" client-secret=\"<CLIENT_SECRET>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..." # Only show first 10 chars
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing Guacamole OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored Guacamole OAuth credentials in Vault"
|
||||
echo ""
|
||||
echo "The External Secret Operator will now sync these credentials to the guacamole namespace."
|
||||
echo "You can verify with:"
|
||||
echo " kubectl get externalsecret -n guacamole guacamole-oauth"
|
||||
echo " kubectl get secret -n guacamole guacamole-oauth-secret"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Guacamole OAuth Sync Complete ==="
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# sync-homeassistant-oauth.sh
|
||||
# Retrieves Home Assistant OIDC provider credentials from Authentik and stores them in Vault
|
||||
# This script should be run after Authentik is deployed and the Home Assistant blueprint is applied
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/homeassistant-oauth"
|
||||
|
||||
echo "=== Syncing Home Assistant OIDC Credentials from Authentik to Vault ==="
|
||||
|
||||
# Check if Authentik is running
|
||||
if ! kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --no-headers 2>/dev/null | grep -q Running; then
|
||||
echo "ERROR: Authentik is not running. Please deploy Authentik first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
# Note: This uses the Authentik API via the management interface
|
||||
echo "Retrieving Home Assistant OIDC credentials from Authentik..."
|
||||
|
||||
# Method 1: Try to get credentials directly from Authentik database
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Home Assistant"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Home Assistant"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If Method 1 fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Query the database for Home Assistant provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -- psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id, client_secret FROM authentik_providers_oauth2_oauth2provider WHERE name='Home Assistant';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "ERROR: Failed to retrieve Home Assistant OIDC credentials from Authentik"
|
||||
echo ""
|
||||
echo "Manual steps required:"
|
||||
echo "1. Access Authentik admin panel at https://auth.kube.huskypup.net"
|
||||
echo "2. Navigate to: Applications > Providers > Home Assistant"
|
||||
echo "3. Copy the Client ID and Client Secret"
|
||||
echo "4. Store them in Vault manually with:"
|
||||
echo " vault kv put ${VAULT_SECRET_PATH} client-id=\"<CLIENT_ID>\" client-secret=\"<CLIENT_SECRET>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..." # Only show first 10 chars
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing Home Assistant OIDC credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored Home Assistant OIDC credentials in Vault"
|
||||
echo ""
|
||||
echo "The External Secret Operator will now sync these credentials to the home-assistant namespace."
|
||||
echo "You can verify with:"
|
||||
echo " kubectl get externalsecret -n home-assistant homeassistant-oauth"
|
||||
echo " kubectl get secret -n home-assistant homeassistant-oidc-secret"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Home Assistant OIDC Sync Complete ==="
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# scripts/sync-n8n-oauth.sh
|
||||
# Sync n8n OAuth credentials from Authentik to Vault
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/n8n-oauth"
|
||||
|
||||
echo "=== Syncing n8n OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
echo "Retrieving n8n OAuth credentials from Authentik..."
|
||||
|
||||
# Try to get credentials via ak command
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "n8n"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "n8n"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If command fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get PostgreSQL password
|
||||
PG_PASSWORD=$(kubectl get secret -n "${AUTHENTIK_NAMESPACE}" pg-authentik-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Query the database for n8n provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -c postgres -- env PGPASSWORD="${PG_PASSWORD}" psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id, client_secret FROM authentik_providers_oauth2_oauth2provider WHERE name='n8n';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "ERROR: Failed to retrieve n8n OAuth credentials from Authentik"
|
||||
echo ""
|
||||
echo "Manual steps required:"
|
||||
echo "1. Access Authentik admin panel at https://auth.kube.huskypup.net"
|
||||
echo "2. Navigate to: Applications > Providers > n8n"
|
||||
echo "3. Copy the Client ID and Client Secret"
|
||||
echo "4. Store them in Vault manually with:"
|
||||
echo " kubectl exec -n vault <vault-pod> -- vault kv put ${VAULT_SECRET_PATH} client-id=\"<CLIENT_ID>\" client-secret=\"<CLIENT_SECRET>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..." # Only show first 10 chars
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing n8n OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored n8n OAuth credentials in Vault"
|
||||
echo ""
|
||||
echo "The External Secret Operator will now sync these credentials to the n8n namespace."
|
||||
echo "You can verify with:"
|
||||
echo " kubectl get externalsecret -n n8n n8n-oauth"
|
||||
echo " kubectl get secret -n n8n n8n-oauth-secret"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== n8n OAuth Sync Complete ==="
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# sync-netbird-oauth.sh
|
||||
# Retrieves Netbird OAuth provider credentials from Authentik and stores them in Vault
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/netbird-oauth"
|
||||
|
||||
echo "=== Syncing Netbird OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Check if Authentik is running
|
||||
if ! kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --no-headers 2>/dev/null | grep -q Running; then
|
||||
echo "ERROR: Authentik is not running. Please deploy Authentik first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
echo "Retrieving Netbird OAuth credentials from Authentik..."
|
||||
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Netbird"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Netbird"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# Fallback: query PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PG_PASS=$(kubectl -n "${AUTHENTIK_NAMESPACE}" get secret pg-authentik-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -- env PGPASSWORD="${PG_PASS}" psql -U app -d app -h localhost -t -c \
|
||||
"SELECT o.client_id, o.client_secret FROM authentik_providers_oauth2_oauth2provider o JOIN authentik_core_provider p ON o.provider_ptr_id = p.id WHERE p.name='Netbird';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "WARNING: Netbird OAuth provider not yet created by blueprint. Will retry on next sync."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..."
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create Authentik service account and API token for Netbird IDP management
|
||||
echo "Ensuring Netbird service account and API token exist in Authentik..."
|
||||
SA_TOKEN=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak shell -c "
|
||||
from authentik.core.models import Token, User, Group
|
||||
try:
|
||||
user = User.objects.get(username='netbird-service')
|
||||
except User.DoesNotExist:
|
||||
user = User.objects.create(
|
||||
username='netbird-service',
|
||||
name='Netbird Service Account',
|
||||
type='service_account',
|
||||
is_active=True,
|
||||
path='goauthentik.io/service-accounts'
|
||||
)
|
||||
# Add to authentik Admins group (required for API access)
|
||||
try:
|
||||
admins = Group.objects.get(name='authentik Admins')
|
||||
admins.users.add(user)
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
try:
|
||||
token = Token.objects.get(user=user, identifier='netbird-idp-manager')
|
||||
except Token.DoesNotExist:
|
||||
token = Token.objects.create(
|
||||
user=user, identifier='netbird-idp-manager', intent='app_password', expiring=False
|
||||
)
|
||||
print(token.key)
|
||||
" 2>&1 | tail -1)
|
||||
|
||||
if [ -z "$SA_TOKEN" ] || [[ "$SA_TOKEN" == *"Error"* ]]; then
|
||||
echo "WARNING: Could not create Netbird service account token. IDP user sync will not work."
|
||||
SA_TOKEN="placeholder"
|
||||
SA_USERNAME="netbird-service"
|
||||
else
|
||||
echo " Service account token: ${SA_TOKEN:0:10}..."
|
||||
SA_USERNAME="netbird-service"
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing Netbird OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- env "VAULT_TOKEN=$(kubectl -n vault get secret vault-init-keys -o jsonpath='{.data.VAULT_ROOT_TOKEN}' | base64 -d)" \
|
||||
vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}" \
|
||||
service-username="${SA_USERNAME}" \
|
||||
service-password="${SA_TOKEN}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored Netbird OAuth credentials in Vault"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Netbird OAuth Sync Complete ==="
|
||||
echo "NOTE: Netbird IDP user sync (cache warming) may show a 400 error."
|
||||
echo "This is a known Netbird/Authentik incompatibility (Netbird uses ROPC grant"
|
||||
echo "which Authentik does not support). OIDC login still works correctly."
|
||||
Executable
+190
@@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
# scripts/sync-nextcloud-oauth.sh
|
||||
# Sync Nextcloud OAuth credentials from Authentik to Vault and configure user_oidc provider
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
NEXTCLOUD_NAMESPACE="nextcloud"
|
||||
VAULT_SECRET_PATH="secret/nextcloud-oauth"
|
||||
AUTHENTIK_URL="https://auth.kube.huskypup.net"
|
||||
NEXTCLOUD_URL="https://nextcloud.kube.huskypup.net"
|
||||
|
||||
echo "=== Syncing Nextcloud OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
echo "Retrieving Nextcloud OAuth credentials from Authentik..."
|
||||
|
||||
# Try to get credentials via ak command
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Nextcloud"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
CLIENT_SECRET=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Nextcloud"' | grep '"client_secret"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If command fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get PostgreSQL password
|
||||
PG_PASSWORD=$(kubectl get secret -n "${AUTHENTIK_NAMESPACE}" pg-authentik-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Query the database for Nextcloud provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -c postgres -- env PGPASSWORD="${PG_PASSWORD}" psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id, client_secret FROM authentik_providers_oauth2_oauth2provider WHERE name='Nextcloud';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | awk '{print $1}' | tr -d ' ')
|
||||
CLIENT_SECRET=$(echo "$PROVIDER_DATA" | awk '{print $3}' | tr -d ' ')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||
echo "ERROR: Failed to retrieve Nextcloud OAuth credentials from Authentik"
|
||||
echo ""
|
||||
echo "Manual steps required:"
|
||||
echo "1. Access Authentik admin panel at ${AUTHENTIK_URL}"
|
||||
echo "2. Navigate to: Applications > Providers > Nextcloud"
|
||||
echo "3. Copy the Client ID and Client Secret"
|
||||
echo "4. Store them in Vault manually with:"
|
||||
echo " kubectl exec -n vault <vault-pod> -- vault kv put ${VAULT_SECRET_PATH} client-id=\"<CLIENT_ID>\" client-secret=\"<CLIENT_SECRET>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
echo " Client Secret: ${CLIENT_SECRET:0:10}..." # Only show first 10 chars
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing Nextcloud OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}" \
|
||||
client-secret="${CLIENT_SECRET}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully stored Nextcloud OAuth credentials in Vault"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for External Secret to sync
|
||||
echo ""
|
||||
echo "Waiting for External Secret to sync credentials to Nextcloud namespace..."
|
||||
sleep 5
|
||||
|
||||
# Verify External Secret synced
|
||||
kubectl wait --for=condition=Ready externalsecret/nextcloud-oauth -n "${NEXTCLOUD_NAMESPACE}" --timeout=30s || {
|
||||
echo "WARNING: External Secret may not be ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Wait for Nextcloud to be ready
|
||||
echo "Waiting for Nextcloud to be ready..."
|
||||
kubectl -n "${NEXTCLOUD_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=nextcloud --timeout=60s || {
|
||||
echo "WARNING: Nextcloud may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Configure Nextcloud user_oidc provider
|
||||
echo ""
|
||||
echo "=== Configuring Nextcloud OIDC Provider ==="
|
||||
|
||||
# Get Nextcloud pod
|
||||
NEXTCLOUD_POD=$(kubectl get pods -n "${NEXTCLOUD_NAMESPACE}" -l app.kubernetes.io/name=nextcloud -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$NEXTCLOUD_POD" ]; then
|
||||
echo "ERROR: Could not find Nextcloud pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Nextcloud pod: ${NEXTCLOUD_POD}"
|
||||
|
||||
# Check if provider already exists
|
||||
EXISTING_PROVIDER=$(kubectl exec -n "${NEXTCLOUD_NAMESPACE}" "${NEXTCLOUD_POD}" -- php occ user_oidc:provider 2>/dev/null | grep -c "Authentik" || true)
|
||||
|
||||
if [ "$EXISTING_PROVIDER" -gt 0 ]; then
|
||||
echo "Updating existing Authentik OIDC provider..."
|
||||
kubectl exec -n "${NEXTCLOUD_NAMESPACE}" "${NEXTCLOUD_POD}" -- php occ user_oidc:provider Authentik \
|
||||
--clientid="${CLIENT_ID}" \
|
||||
--clientsecret="${CLIENT_SECRET}" \
|
||||
--discoveryuri="${AUTHENTIK_URL}/application/o/nextcloud/.well-known/openid-configuration" \
|
||||
--scope="openid email profile" \
|
||||
--mapping-uid="preferred_username" \
|
||||
--mapping-display-name="name" \
|
||||
--mapping-email="email" \
|
||||
--unique-uid=0
|
||||
else
|
||||
echo "Creating new Authentik OIDC provider..."
|
||||
kubectl exec -n "${NEXTCLOUD_NAMESPACE}" "${NEXTCLOUD_POD}" -- php occ user_oidc:provider Authentik \
|
||||
--clientid="${CLIENT_ID}" \
|
||||
--clientsecret="${CLIENT_SECRET}" \
|
||||
--discoveryuri="${AUTHENTIK_URL}/application/o/nextcloud/.well-known/openid-configuration" \
|
||||
--scope="openid email profile" \
|
||||
--mapping-uid="preferred_username" \
|
||||
--mapping-display-name="name" \
|
||||
--mapping-email="email" \
|
||||
--unique-uid=0
|
||||
fi
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Successfully configured Nextcloud OIDC provider"
|
||||
else
|
||||
echo "ERROR: Failed to configure Nextcloud OIDC provider"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify configuration
|
||||
echo ""
|
||||
echo "Verifying OIDC provider configuration..."
|
||||
kubectl exec -n "${NEXTCLOUD_NAMESPACE}" "${NEXTCLOUD_POD}" -- php occ user_oidc:provider Authentik
|
||||
|
||||
echo ""
|
||||
echo "=== Nextcloud OAuth Configuration Complete ==="
|
||||
echo ""
|
||||
echo "You can now:"
|
||||
echo "1. Visit ${NEXTCLOUD_URL}"
|
||||
echo "2. Click 'Log in with Authentik' button on the login page"
|
||||
echo "3. Authenticate using your Authentik credentials"
|
||||
echo ""
|
||||
echo "To verify the configuration:"
|
||||
echo " kubectl get externalsecret -n ${NEXTCLOUD_NAMESPACE} nextcloud-oauth"
|
||||
echo " kubectl get secret -n ${NEXTCLOUD_NAMESPACE} nextcloud-oauth-secret"
|
||||
echo " kubectl exec -n ${NEXTCLOUD_NAMESPACE} ${NEXTCLOUD_POD} -- php occ user_oidc:provider"
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# sync-percona-everest-oauth.sh
|
||||
# Retrieves Percona Everest OIDC provider credentials from Authentik and stores them in Vault
|
||||
# Everest uses PKCE (public client) so the client_secret is not strictly required,
|
||||
# but we store it in Vault for reference and potential future use.
|
||||
|
||||
AUTHENTIK_NAMESPACE="authentik"
|
||||
VAULT_NAMESPACE="vault"
|
||||
VAULT_SECRET_PATH="secret/percona-everest-oauth"
|
||||
|
||||
echo "=== Syncing Percona Everest OAuth Credentials from Authentik to Vault ==="
|
||||
|
||||
# Check if Authentik is running
|
||||
if ! kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --no-headers 2>/dev/null | grep -q Running; then
|
||||
echo "ERROR: Authentik is not running. Please deploy Authentik first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for Authentik to be fully ready
|
||||
echo "Waiting for Authentik to be ready..."
|
||||
kubectl -n "${AUTHENTIK_NAMESPACE}" wait --for=condition=Ready pod -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server --timeout=60s || {
|
||||
echo "WARNING: Authentik may not be fully ready yet. Continuing anyway..."
|
||||
}
|
||||
|
||||
# Get Authentik pod name
|
||||
AUTHENTIK_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l app.kubernetes.io/name=authentik,app.kubernetes.io/component=server -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$AUTHENTIK_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik server pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found Authentik pod: ${AUTHENTIK_POD}"
|
||||
|
||||
# Retrieve OAuth2 provider credentials from Authentik
|
||||
echo "Retrieving Percona Everest OAuth credentials from Authentik..."
|
||||
|
||||
# Method 1: Try to get credentials directly from Authentik CLI
|
||||
CLIENT_ID=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${AUTHENTIK_POD}" -- ak list_providers --type oauth2 2>/dev/null | grep -A 20 '"name": "Percona Everest"' | grep '"client_id"' | cut -d'"' -f4 || echo "")
|
||||
|
||||
# If Method 1 fails, try using PostgreSQL directly
|
||||
if [ -z "$CLIENT_ID" ]; then
|
||||
echo "Attempting to retrieve credentials from Authentik PostgreSQL database..."
|
||||
|
||||
CNPG_POD=$(kubectl get pods -n "${AUTHENTIK_NAMESPACE}" -l cnpg.io/cluster=pg-authentik,role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$CNPG_POD" ]; then
|
||||
echo "ERROR: Could not find Authentik PostgreSQL pod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Query the database for Percona Everest provider credentials
|
||||
PROVIDER_DATA=$(kubectl exec -n "${AUTHENTIK_NAMESPACE}" "${CNPG_POD}" -- psql -U authentik -d authentik -t -c \
|
||||
"SELECT client_id FROM authentik_providers_oauth2_oauth2provider WHERE name='Percona Everest';" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$PROVIDER_DATA" ]; then
|
||||
CLIENT_ID=$(echo "$PROVIDER_DATA" | tr -d ' ' | tr -d '\n')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate credentials were retrieved
|
||||
if [ -z "$CLIENT_ID" ]; then
|
||||
echo "WARNING: Could not retrieve Percona Everest OAuth credentials from Authentik"
|
||||
echo "The OIDC provider may not be configured yet (blueprint not processed)."
|
||||
echo "Everest will use local admin authentication until OIDC is available."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Successfully retrieved credentials:"
|
||||
echo " Client ID: ${CLIENT_ID}"
|
||||
|
||||
# Check if Vault is unsealed and ready
|
||||
echo "Checking Vault status..."
|
||||
VAULT_POD=$(kubectl get pods -n "${VAULT_NAMESPACE}" -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$VAULT_POD" ]; then
|
||||
echo "ERROR: Vault pod not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAULT_STATUS=$(kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault status -format=json 2>/dev/null || echo "{}")
|
||||
SEALED=$(echo "$VAULT_STATUS" | grep -o '"sealed":[^,}]*' | cut -d':' -f2 | tr -d ' ')
|
||||
|
||||
if [ "$SEALED" = "true" ]; then
|
||||
echo "ERROR: Vault is sealed. Please unseal Vault first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store credentials in Vault
|
||||
echo "Storing Percona Everest OAuth credentials in Vault at ${VAULT_SECRET_PATH}..."
|
||||
kubectl exec -n "${VAULT_NAMESPACE}" "${VAULT_POD}" -- vault kv put "${VAULT_SECRET_PATH}" \
|
||||
client-id="${CLIENT_ID}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully stored Percona Everest OAuth credentials in Vault"
|
||||
else
|
||||
echo "ERROR: Failed to store credentials in Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Percona Everest OAuth Sync Complete ==="
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export TALOSCONFIG=/home/scooby/Talosv3/talosconfig
|
||||
|
||||
echo "=== Talos iSCSI Bootstrap Script ==="
|
||||
echo ""
|
||||
echo "This script configures Talos nodes for Longhorn by:"
|
||||
echo "1. Enabling control plane scheduling"
|
||||
echo "2. Adding Longhorn volume mounts"
|
||||
echo "3. Upgrading to factory image with iscsi-tools"
|
||||
echo ""
|
||||
|
||||
# Step 1: Enable scheduling on control planes
|
||||
echo "Step 1/4: Enabling scheduling on control planes..."
|
||||
cat > /tmp/allow-scheduling.yaml <<EOF
|
||||
cluster:
|
||||
allowSchedulingOnControlPlanes: true
|
||||
EOF
|
||||
|
||||
talosctl patch mc -p @/tmp/allow-scheduling.yaml --nodes 172.28.101.41,172.28.101.42,172.28.101.43,172.28.101.44
|
||||
echo "✅ Scheduling enabled"
|
||||
|
||||
# Step 2: Add Longhorn volume mounts
|
||||
echo ""
|
||||
echo "Step 2/4: Adding Longhorn volume mounts..."
|
||||
cat > /tmp/longhorn-volume.patch.yaml <<EOF
|
||||
machine:
|
||||
kubelet:
|
||||
extraMounts:
|
||||
- destination: /var/lib/longhorn
|
||||
type: bind
|
||||
source: /var/lib/longhorn
|
||||
options:
|
||||
- bind
|
||||
- rshared
|
||||
- rw
|
||||
EOF
|
||||
|
||||
talosctl patch mc --nodes 172.28.101.41,172.28.101.42,172.28.101.43,172.28.101.44 --patch @/tmp/longhorn-volume.patch.yaml
|
||||
echo "✅ Longhorn mounts configured"
|
||||
|
||||
# Step 3: Remove old extension references
|
||||
echo ""
|
||||
echo "Step 3/4: Removing deprecated extension references..."
|
||||
cat > /tmp/clear-extensions.yaml <<EOF
|
||||
- op: remove
|
||||
path: /machine/install/extensions
|
||||
EOF
|
||||
|
||||
talosctl patch mc --patch @/tmp/clear-extensions.yaml --nodes 172.28.101.41,172.28.101.42,172.28.101.43,172.28.101.44 2>/dev/null || true
|
||||
echo "✅ Old extensions removed"
|
||||
|
||||
# Step 4: Upgrade to factory image with iscsi-tools
|
||||
echo ""
|
||||
echo "Step 4/4: Upgrading to Talos with iscsi-tools (this will take several minutes)..."
|
||||
echo "Factory image includes: iscsi-tools, util-linux-tools"
|
||||
echo ""
|
||||
|
||||
SCHEMATIC_ID="613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245"
|
||||
TALOS_VERSION="v1.9.4"
|
||||
|
||||
echo "Upgrading nodes one at a time to: factory.talos.dev/installer/$SCHEMATIC_ID:$TALOS_VERSION"
|
||||
|
||||
for node in 172.28.101.41 172.28.101.42 172.28.101.43 172.28.101.44; do
|
||||
echo ""
|
||||
echo "Upgrading $node..."
|
||||
talosctl upgrade --nodes $node \
|
||||
--image factory.talos.dev/installer/$SCHEMATIC_ID:$TALOS_VERSION \
|
||||
--preserve --wait --timeout 10m || echo "⚠️ Node $node upgrade encountered issues, continuing..."
|
||||
|
||||
echo "Waiting 30s for node to stabilize..."
|
||||
sleep 30
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Waiting for all nodes to be Ready ==="
|
||||
kubectl wait --for=condition=Ready nodes --all --timeout=300s
|
||||
|
||||
echo ""
|
||||
echo "=== Verifying iSCSI installation ==="
|
||||
talosctl get extensions -n 172.28.101.41
|
||||
|
||||
echo ""
|
||||
echo "✅ Talos iSCSI bootstrap complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " cd /home/scooby/Homelabv5"
|
||||
echo " ./scripts/bootstrap-crds.sh"
|
||||
echo " helmfile apply"
|
||||
Executable
+131
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bash
|
||||
# Teslamate Bootstrap - Auto-configure PostgreSQL with ESO
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Teslamate Bootstrap - Fully Automated ==="
|
||||
|
||||
# Change to the apps directory for relative paths
|
||||
cd "$(dirname "$0")/../apps" || exit 1
|
||||
|
||||
NS=teslamate
|
||||
CLUSTER=pg-teslamate
|
||||
|
||||
# 1) Ensure namespace
|
||||
kubectl get ns "${NS}" >/dev/null 2>&1 || kubectl create ns "${NS}"
|
||||
|
||||
# 2) Apply CNPG cluster (auto-generates DB password)
|
||||
echo "Applying Teslamate PostgreSQL cluster..."
|
||||
kubectl apply -f teslamate/cnpg-cluster.yaml
|
||||
|
||||
# 3) Wait for CNPG cluster
|
||||
echo "Waiting for CNPG cluster ${CLUSTER}..."
|
||||
kubectl -n "${NS}" wait --for=condition=Ready "cluster/${CLUSTER}" --timeout=300s
|
||||
|
||||
# 3.5) Set up PostgreSQL extensions and grant superuser
|
||||
echo "Setting up PostgreSQL extensions for Teslamate..."
|
||||
PRIMARY_POD=$(kubectl get pod -n "${NS}" -l cnpg.io/cluster="${CLUSTER}",role=primary -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -n "$PRIMARY_POD" ]; then
|
||||
echo "Granting superuser to teslamate user..."
|
||||
kubectl exec -n "${NS}" "$PRIMARY_POD" -- psql -U postgres -c "ALTER USER teslamate WITH SUPERUSER;" 2>/dev/null || echo " (user may already have superuser)"
|
||||
|
||||
echo "Creating required PostgreSQL extensions..."
|
||||
kubectl exec -n "${NS}" "$PRIMARY_POD" -- psql -U postgres -d teslamate -c "CREATE EXTENSION IF NOT EXISTS cube;" 2>/dev/null || echo " (cube extension may already exist)"
|
||||
kubectl exec -n "${NS}" "$PRIMARY_POD" -- psql -U postgres -d teslamate -c "CREATE EXTENSION IF NOT EXISTS earthdistance;" 2>/dev/null || echo " (earthdistance extension may already exist)"
|
||||
|
||||
echo "✅ PostgreSQL extensions configured"
|
||||
else
|
||||
echo "⚠️ Warning: Could not find primary pod, skipping extension setup"
|
||||
fi
|
||||
|
||||
# 4) Apply ESO secrets (auto-rotate DB passwords)
|
||||
echo "Applying Teslamate ESO secrets (auto-generates passwords)..."
|
||||
kubectl apply -f teslamate/cnpg-secrets.yaml
|
||||
|
||||
# 5) Wait for password to be generated
|
||||
echo "Waiting for ESO to generate database password..."
|
||||
for i in {1..30}; do
|
||||
if kubectl -n "${NS}" get secret pg-teslamate-app >/dev/null 2>&1; then
|
||||
echo "✅ Database password generated"
|
||||
break
|
||||
fi
|
||||
echo " waiting for secret... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 6) Initialize encryption key in Vault if it doesn't exist
|
||||
echo "Initializing encryption key in Vault..."
|
||||
|
||||
# Get Vault root token from secret
|
||||
VAULT_ROOT_TOKEN=$(kubectl -n vault get secret vault-init-keys -o jsonpath='{.data.VAULT_ROOT_TOKEN}' 2>/dev/null | base64 -d || echo "")
|
||||
|
||||
if [ -z "$VAULT_ROOT_TOKEN" ]; then
|
||||
echo "⚠️ Warning: Could not retrieve Vault root token from secret vault-init-keys"
|
||||
echo "Skipping encryption key creation. You'll need to create it manually."
|
||||
echo ""
|
||||
echo "To create the encryption key manually:"
|
||||
echo " ENCRYPTION_KEY=\$(openssl rand -base64 32)"
|
||||
echo " kubectl exec -n vault vault-0 -- vault kv put secret/teslamate/config encryption_key=\"\${ENCRYPTION_KEY}\""
|
||||
echo ""
|
||||
# Continue anyway - the ExternalSecret will show an error if the key doesn't exist
|
||||
else
|
||||
# Check if the secret already exists in Vault
|
||||
if kubectl exec -n vault vault-0 -- env VAULT_TOKEN="${VAULT_ROOT_TOKEN}" vault kv get secret/teslamate/config >/dev/null 2>&1; then
|
||||
echo "✅ Encryption key already exists in Vault"
|
||||
else
|
||||
echo "Generating new encryption key..."
|
||||
ENCRYPTION_KEY=$(openssl rand -base64 32)
|
||||
|
||||
# Write to Vault using root token
|
||||
if kubectl exec -n vault vault-0 -- env VAULT_TOKEN="${VAULT_ROOT_TOKEN}" vault kv put secret/teslamate/config encryption_key="${ENCRYPTION_KEY}"; then
|
||||
echo "✅ Encryption key stored in Vault"
|
||||
else
|
||||
echo "❌ Failed to write to Vault"
|
||||
echo "Please create the encryption key manually:"
|
||||
echo " kubectl exec -n vault vault-0 -- env VAULT_TOKEN=\"${VAULT_ROOT_TOKEN}\" vault kv put secret/teslamate/config encryption_key=\"${ENCRYPTION_KEY}\""
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 7) Apply Teslamate config ExternalSecret
|
||||
echo "Applying Teslamate config ExternalSecret..."
|
||||
kubectl apply -f teslamate/external-secret.yaml
|
||||
|
||||
# 8) Wait for teslamate-config-secret to be created
|
||||
echo "Waiting for teslamate-config-secret to be synced from Vault..."
|
||||
SECRET_SYNCED=false
|
||||
for i in {1..30}; do
|
||||
if kubectl -n "${NS}" get secret teslamate-config-secret >/dev/null 2>&1; then
|
||||
echo "✅ teslamate-config-secret synced successfully"
|
||||
SECRET_SYNCED=true
|
||||
break
|
||||
fi
|
||||
echo " waiting for secret... (attempt $i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 9) If ESO failed to sync, create secret manually as fallback
|
||||
if [ "$SECRET_SYNCED" = "false" ]; then
|
||||
echo "⚠️ ExternalSecret failed to sync (ClusterSecretStore issue)"
|
||||
echo "Creating teslamate-config-secret manually from Vault..."
|
||||
|
||||
if [ -n "$VAULT_ROOT_TOKEN" ]; then
|
||||
ENCRYPTION_KEY=$(kubectl exec -n vault vault-0 -- env VAULT_TOKEN="${VAULT_ROOT_TOKEN}" vault kv get -field=encryption_key secret/teslamate/config 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$ENCRYPTION_KEY" ]; then
|
||||
kubectl create secret generic teslamate-config-secret -n "${NS}" \
|
||||
--from-literal=encryption-key="${ENCRYPTION_KEY}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
echo "✅ Created teslamate-config-secret manually"
|
||||
else
|
||||
echo "❌ Failed to retrieve encryption key from Vault"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Cannot create secret manually - no Vault token"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✅ Teslamate bootstrap complete - fully automated!"
|
||||
@@ -0,0 +1,233 @@
|
||||
---
|
||||
# V1 PVC (traditional iSCSI engine)
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: benchmark-v1-pvc
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn # V1 engine (default)
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
---
|
||||
# V2 PVC (SPDK engine)
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: benchmark-v2-pvc
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn-v2 # V2 SPDK engine
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
---
|
||||
# Benchmark scripts ConfigMap
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: v1-v2-benchmark-scripts
|
||||
namespace: default
|
||||
data:
|
||||
run-benchmarks.sh: |
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ENGINE=$1
|
||||
DATA_DIR=$2
|
||||
|
||||
echo "=========================================="
|
||||
echo "Longhorn $ENGINE Data Engine Benchmark"
|
||||
echo "=========================================="
|
||||
echo "Test file: $DATA_DIR/testfile (10GB)"
|
||||
echo ""
|
||||
|
||||
# Sequential Write
|
||||
echo "1. Sequential Write Test..."
|
||||
fio --name=seq-write \
|
||||
--filename=$DATA_DIR/testfile \
|
||||
--size=10G \
|
||||
--rw=write \
|
||||
--bs=1M \
|
||||
--direct=1 \
|
||||
--numjobs=1 \
|
||||
--time_based \
|
||||
--runtime=60 \
|
||||
--group_reporting \
|
||||
--output-format=normal | tee /tmp/seq-write.log
|
||||
|
||||
SEQ_WRITE_BW=$(grep "WRITE:" /tmp/seq-write.log | awk '{print $2}' | sed 's/bw=//;s/,//')
|
||||
SEQ_WRITE_IOPS=$(grep "WRITE:" /tmp/seq-write.log | awk '{print $4}' | sed 's/IOPS=//;s/,//')
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Sequential Read
|
||||
echo "2. Sequential Read Test..."
|
||||
fio --name=seq-read \
|
||||
--filename=$DATA_DIR/testfile \
|
||||
--rw=read \
|
||||
--bs=1M \
|
||||
--direct=1 \
|
||||
--numjobs=1 \
|
||||
--time_based \
|
||||
--runtime=60 \
|
||||
--group_reporting \
|
||||
--output-format=normal | tee /tmp/seq-read.log
|
||||
|
||||
SEQ_READ_BW=$(grep "READ:" /tmp/seq-read.log | awk '{print $2}' | sed 's/bw=//;s/,//')
|
||||
SEQ_READ_IOPS=$(grep "READ:" /tmp/seq-read.log | awk '{print $4}' | sed 's/IOPS=//;s/,//')
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Random Write
|
||||
echo "3. Random Write Test (4K blocks)..."
|
||||
fio --name=rand-write \
|
||||
--filename=$DATA_DIR/testfile \
|
||||
--rw=randwrite \
|
||||
--bs=4k \
|
||||
--direct=1 \
|
||||
--numjobs=4 \
|
||||
--time_based \
|
||||
--runtime=60 \
|
||||
--group_reporting \
|
||||
--output-format=normal | tee /tmp/rand-write.log
|
||||
|
||||
RAND_WRITE_IOPS=$(grep "WRITE:" /tmp/rand-write.log | awk '{print $4}' | sed 's/IOPS=//;s/,//')
|
||||
RAND_WRITE_BW=$(grep "WRITE:" /tmp/rand-write.log | awk '{print $2}' | sed 's/bw=//;s/,//')
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Random Read
|
||||
echo "4. Random Read Test (4K blocks)..."
|
||||
fio --name=rand-read \
|
||||
--filename=$DATA_DIR/testfile \
|
||||
--rw=randread \
|
||||
--bs=4k \
|
||||
--direct=1 \
|
||||
--numjobs=4 \
|
||||
--time_based \
|
||||
--runtime=60 \
|
||||
--group_reporting \
|
||||
--output-format=normal | tee /tmp/rand-read.log
|
||||
|
||||
RAND_READ_IOPS=$(grep "READ:" /tmp/rand-read.log | awk '{print $4}' | sed 's/IOPS=//;s/,//')
|
||||
RAND_READ_BW=$(grep "READ:" /tmp/rand-read.log | awk '{print $2}' | sed 's/bw=//;s/,//')
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "$ENGINE Engine Results Summary"
|
||||
echo "=========================================="
|
||||
echo "Sequential Write: $SEQ_WRITE_BW ($SEQ_WRITE_IOPS IOPS)"
|
||||
echo "Sequential Read: $SEQ_READ_BW ($SEQ_READ_IOPS IOPS)"
|
||||
echo "Random Write: $RAND_WRITE_BW ($RAND_WRITE_IOPS IOPS)"
|
||||
echo "Random Read: $RAND_READ_BW ($RAND_READ_IOPS IOPS)"
|
||||
echo "=========================================="
|
||||
---
|
||||
# V1 Benchmark Job
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: benchmark-v1
|
||||
namespace: default
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 3600
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: benchmark
|
||||
engine: v1
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: fio
|
||||
image: ljishen/fio:latest
|
||||
command: ["/bin/sh"]
|
||||
args: ["/scripts/run-benchmarks.sh", "V1", "/data"]
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: "2Gi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "4Gi"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: scripts
|
||||
mountPath: /scripts
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: benchmark-v1-pvc
|
||||
- name: scripts
|
||||
configMap:
|
||||
name: v1-v2-benchmark-scripts
|
||||
defaultMode: 0755
|
||||
---
|
||||
# V2 Benchmark Job
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: benchmark-v2
|
||||
namespace: default
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 3600
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: benchmark
|
||||
engine: v2
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: fio
|
||||
image: ljishen/fio:latest
|
||||
command: ["/bin/sh"]
|
||||
args: ["/scripts/run-benchmarks.sh", "V2", "/data"]
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: "2Gi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "4Gi"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: scripts
|
||||
mountPath: /scripts
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: benchmark-v2-pvc
|
||||
- name: scripts
|
||||
configMap:
|
||||
name: v1-v2-benchmark-scripts
|
||||
defaultMode: 0755
|
||||
Executable
+199
@@ -0,0 +1,199 @@
|
||||
#!/bin/bash
|
||||
# scripts/validate-n8n-deployment.sh
|
||||
# Validate n8n deployment and SCCM automation setup
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=========================================="
|
||||
echo "n8n Deployment Validation"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
validate_step() {
|
||||
local step_name=$1
|
||||
local command=$2
|
||||
|
||||
echo -n "Checking $step_name... "
|
||||
|
||||
if eval "$command" &>/dev/null; then
|
||||
echo -e "${GREEN}✓ PASS${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ FAIL${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_step_with_output() {
|
||||
local step_name=$1
|
||||
local command=$2
|
||||
|
||||
echo "Checking $step_name..."
|
||||
|
||||
if output=$(eval "$command" 2>&1); then
|
||||
echo -e "${GREEN}✓ PASS${NC}"
|
||||
echo "$output"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ FAIL${NC}"
|
||||
echo "$output"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Track failures
|
||||
FAILURES=0
|
||||
|
||||
# 1. Check namespace exists
|
||||
if ! validate_step "n8n namespace exists" "kubectl get namespace n8n"; then
|
||||
((FAILURES++))
|
||||
echo -e "${YELLOW} → Run: helmfile -l name=n8n apply${NC}"
|
||||
fi
|
||||
|
||||
# 2. Check external secrets
|
||||
echo ""
|
||||
echo "Checking External Secrets..."
|
||||
if kubectl get namespace n8n &>/dev/null; then
|
||||
if ! validate_step " PostgreSQL secret" "kubectl get externalsecret -n n8n n8n-postgresql"; then
|
||||
((FAILURES++))
|
||||
echo -e "${YELLOW} → Run: kubectl apply -f kubernetes/apps/n8n/external-secret.yaml${NC}"
|
||||
fi
|
||||
|
||||
if ! validate_step " Config secret" "kubectl get externalsecret -n n8n n8n-config"; then
|
||||
((FAILURES++))
|
||||
echo -e "${YELLOW} → Run: kubectl apply -f kubernetes/apps/n8n/external-secret.yaml${NC}"
|
||||
fi
|
||||
|
||||
# Check if secrets are synced
|
||||
if ! validate_step " PostgreSQL secret synced" "kubectl get secret -n n8n n8n-postgresql-secret"; then
|
||||
((FAILURES++))
|
||||
echo -e "${YELLOW} → Ensure Vault has secrets. Run: ./scripts/init-n8n-secrets.sh${NC}"
|
||||
fi
|
||||
|
||||
if ! validate_step " Config secret synced" "kubectl get secret -n n8n n8n-config-secret"; then
|
||||
((FAILURES++))
|
||||
echo -e "${YELLOW} → Ensure Vault has secrets. Run: ./scripts/init-n8n-secrets.sh${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Check PostgreSQL deployment
|
||||
echo ""
|
||||
echo "Checking PostgreSQL..."
|
||||
if ! validate_step " PostgreSQL StatefulSet" "kubectl get statefulset -n n8n n8n-postgresql"; then
|
||||
((FAILURES++))
|
||||
fi
|
||||
|
||||
if kubectl get statefulset -n n8n n8n-postgresql &>/dev/null; then
|
||||
if ! validate_step " PostgreSQL pod ready" "kubectl wait --for=condition=ready pod -n n8n -l app.kubernetes.io/name=postgresql --timeout=10s"; then
|
||||
((FAILURES++))
|
||||
echo -e "${YELLOW} → Check: kubectl logs -n n8n n8n-postgresql-0${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. Check n8n deployment
|
||||
echo ""
|
||||
echo "Checking n8n deployment..."
|
||||
if ! validate_step " n8n deployment exists" "kubectl get deployment -n n8n n8n"; then
|
||||
((FAILURES++))
|
||||
else
|
||||
if ! validate_step " n8n pod ready" "kubectl wait --for=condition=ready pod -n n8n -l app.kubernetes.io/name=n8n --timeout=10s"; then
|
||||
((FAILURES++))
|
||||
echo -e "${YELLOW} → Check: kubectl logs -n n8n deployment/n8n${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. Check service
|
||||
echo ""
|
||||
if ! validate_step "n8n service exists" "kubectl get service -n n8n n8n"; then
|
||||
((FAILURES++))
|
||||
fi
|
||||
|
||||
# 6. Check ingress
|
||||
echo ""
|
||||
if ! validate_step "n8n ingress exists" "kubectl get ingress -n n8n"; then
|
||||
((FAILURES++))
|
||||
fi
|
||||
|
||||
# 7. Check ingress hostname
|
||||
echo ""
|
||||
if kubectl get ingress -n n8n &>/dev/null; then
|
||||
INGRESS_HOST=$(kubectl get ingress -n n8n -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "")
|
||||
if [ -n "$INGRESS_HOST" ]; then
|
||||
echo -e "n8n ingress hostname: ${GREEN}$INGRESS_HOST${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ No ingress hostname found${NC}"
|
||||
((FAILURES++))
|
||||
fi
|
||||
fi
|
||||
|
||||
# 8. Check TLS certificate
|
||||
echo ""
|
||||
if ! validate_step "n8n TLS certificate" "kubectl get certificate -n n8n n8n-tls"; then
|
||||
((FAILURES++))
|
||||
else
|
||||
CERT_READY=$(kubectl get certificate -n n8n n8n-tls -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "False")
|
||||
if [ "$CERT_READY" = "True" ]; then
|
||||
echo -e " Certificate status: ${GREEN}Ready${NC}"
|
||||
else
|
||||
echo -e " Certificate status: ${YELLOW}Not Ready${NC}"
|
||||
echo -e "${YELLOW} → Check: kubectl describe certificate -n n8n n8n-tls${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 9. Check PVC
|
||||
echo ""
|
||||
if ! validate_step "n8n PVC exists" "kubectl get pvc -n n8n"; then
|
||||
((FAILURES++))
|
||||
fi
|
||||
|
||||
# 10. Test n8n health
|
||||
echo ""
|
||||
echo "Testing n8n connectivity..."
|
||||
if [ -n "$INGRESS_HOST" ]; then
|
||||
if HTTP_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" "https://$INGRESS_HOST" 2>/dev/null); then
|
||||
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 302 ]; then
|
||||
echo -e " HTTP response: ${GREEN}$HTTP_CODE${NC}"
|
||||
else
|
||||
echo -e " HTTP response: ${YELLOW}$HTTP_CODE${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW} → Could not connect to $INGRESS_HOST${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
if [ $FAILURES -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All checks passed!${NC}"
|
||||
echo ""
|
||||
echo "n8n is ready at: https://$INGRESS_HOST"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Access n8n and create admin account"
|
||||
echo "2. Install SCCM API service on Windows: sccm-automation/Install-SCCMAPIService.ps1"
|
||||
echo "3. Import workflow: sccm-automation/firefox-automation-workflow.json"
|
||||
echo "4. Configure credentials in n8n"
|
||||
echo "5. Activate the workflow"
|
||||
echo ""
|
||||
echo "See: sccm-automation/QUICKSTART.md for detailed setup"
|
||||
else
|
||||
echo -e "${RED}✗ $FAILURES check(s) failed${NC}"
|
||||
echo ""
|
||||
echo "Please review the errors above and take corrective action."
|
||||
echo ""
|
||||
echo "Common fixes:"
|
||||
echo "1. Initialize secrets: ./scripts/init-n8n-secrets.sh"
|
||||
echo "2. Deploy n8n: helmfile -l name=n8n apply"
|
||||
echo "3. Check logs: kubectl logs -n n8n deployment/n8n"
|
||||
echo "4. Check events: kubectl get events -n n8n --sort-by='.lastTimestamp'"
|
||||
fi
|
||||
echo "=========================================="
|
||||
|
||||
exit $FAILURES
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
VAULT_NS="${VAULT_NS:-vault}"
|
||||
SECRET_NAME="${SECRET_NAME:-vault-init-keys}"
|
||||
KV_MOUNT="${KV_MOUNT:-secret}"
|
||||
JOB_NS_FOR_ROLE="${JOB_NS_FOR_ROLE:-auth-proxy}"
|
||||
JOB_SA_FOR_ROLE="${JOB_SA_FOR_ROLE:-oauth2-bootstrap}"
|
||||
POLICY_NAME="${POLICY_NAME:-oauth2-writer}"
|
||||
ROLE_NAME="${ROLE_NAME:-eso-writer}"
|
||||
|
||||
# --- prereqs ---
|
||||
command -v kubectl >/dev/null || { echo "kubectl not found"; exit 1; }
|
||||
command -v vault >/dev/null || { echo "vault CLI not found"; exit 1; }
|
||||
command -v jq >/dev/null || { echo "jq not found"; exit 1; }
|
||||
|
||||
# --- port-forward Vault locally ---
|
||||
echo "==> Port-forwarding Vault service (ctrl-c in another terminal to stop when done)"
|
||||
kubectl -n "$VAULT_NS" port-forward svc/vault 8200:8200 >/dev/null 2>&1 &
|
||||
pf_pid=$!
|
||||
trap 'kill $pf_pid >/dev/null 2>&1 || true' EXIT
|
||||
sleep 2
|
||||
|
||||
export VAULT_ADDR="http://127.0.0.1:8200"
|
||||
|
||||
# --- check init/seal status ---
|
||||
status_json="$(vault status -format=json || true)"
|
||||
initialized="$(jq -r '.initialized // empty' <<<"$status_json")"
|
||||
sealed="$(jq -r '.sealed // empty' <<<"$status_json")"
|
||||
|
||||
root_token=""
|
||||
unseal_key=""
|
||||
|
||||
# pull existing secret if present
|
||||
if kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" >/dev/null 2>&1; then
|
||||
root_token="$(kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" -o jsonpath='{.data.VAULT_ROOT_TOKEN}' | base64 -d || true)"
|
||||
unseal_key="$(kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" -o jsonpath='{.data.VAULT_UNSEAL_KEY}' | base64 -d || true)"
|
||||
fi
|
||||
|
||||
# initialize if needed
|
||||
if [[ "$initialized" != "true" ]]; then
|
||||
echo "==> Vault not initialized; initializing..."
|
||||
init_json="$(vault operator init -key-shares=1 -key-threshold=1 -format=json)"
|
||||
root_token="$(jq -r .root_token <<<"$init_json")"
|
||||
unseal_key="$(jq -r '.unseal_keys_b64[0]' <<<"$init_json")"
|
||||
sealed="true"
|
||||
|
||||
echo "==> Storing root token & unseal key in Secret ${VAULT_NS}/${SECRET_NAME}"
|
||||
kubectl -n "$VAULT_NS" create secret generic "$SECRET_NAME" \
|
||||
--from-literal=VAULT_ROOT_TOKEN="$root_token" \
|
||||
--from-literal=VAULT_UNSEAL_KEY="$unseal_key" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
else
|
||||
echo "==> Vault already initialized."
|
||||
fi
|
||||
|
||||
# unseal if needed
|
||||
if [[ "$sealed" == "true" ]]; then
|
||||
[[ -n "$unseal_key" ]] || { echo "ERROR: sealed and no unseal key available"; exit 1; }
|
||||
echo "==> Unsealing..."
|
||||
vault operator unseal "$unseal_key" >/dev/null
|
||||
fi
|
||||
|
||||
# login
|
||||
if [[ -z "$root_token" ]]; then
|
||||
echo "==> Reading root token from Secret..."
|
||||
root_token="$(kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" -o jsonpath='{.data.VAULT_ROOT_TOKEN}' | base64 -d)"
|
||||
fi
|
||||
vault login "$root_token" >/dev/null
|
||||
|
||||
# ensure KV v2 enabled
|
||||
if ! vault secrets list -format=json | jq -e "has(\"${KV_MOUNT}/\")" >/dev/null; then
|
||||
echo "==> Enabling KV v2 at ${KV_MOUNT}/"
|
||||
vault secrets enable -path="$KV_MOUNT" -version=2 kv >/dev/null
|
||||
fi
|
||||
|
||||
# configure Kubernetes auth with a reviewer token from a local SA
|
||||
echo "==> Ensuring reviewer SA + binding"
|
||||
kubectl -n "$VAULT_NS" get sa vault-auth >/dev/null 2>&1 || kubectl -n "$VAULT_NS" create sa vault-auth
|
||||
kubectl get clusterrolebinding vault-auth-delegator >/dev/null 2>&1 || \
|
||||
kubectl create clusterrolebinding vault-auth-delegator \
|
||||
--clusterrole=system:auth-delegator \
|
||||
--serviceaccount="${VAULT_NS}:vault-auth"
|
||||
|
||||
reviewer_jwt="$(kubectl -n "$VAULT_NS" create token vault-auth)"
|
||||
kube_ca="$(kubectl -n kube-system get configmap kube-root-ca.crt -o jsonpath='{.data.ca\.crt}')"
|
||||
kube_host="https://kubernetes.default.svc:443"
|
||||
|
||||
vault auth enable kubernetes >/dev/null 2>&1 || true
|
||||
vault write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$reviewer_jwt" \
|
||||
kubernetes_host="$kube_host" \
|
||||
kubernetes_ca_cert="$kube_ca" >/dev/null
|
||||
|
||||
# policy + role for ESO/oauth2 job
|
||||
vault policy write "$POLICY_NAME" - >/dev/null <<'HCL'
|
||||
path "secret/data/*" {
|
||||
capabilities = ["create", "update", "read", "list"]
|
||||
}
|
||||
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["create", "update", "read", "list"]
|
||||
}
|
||||
HCL
|
||||
|
||||
vault write auth/kubernetes/role/${ROLE_NAME} \
|
||||
bound_service_account_names="${JOB_SA_FOR_ROLE},external-secrets" \
|
||||
bound_service_account_namespaces="${JOB_NS_FOR_ROLE},external-secrets" \
|
||||
policies="${POLICY_NAME}" \
|
||||
ttl="24h" >/dev/null
|
||||
|
||||
echo "==> Done."
|
||||
echo "K8s Secret with init creds: ${VAULT_NS}/${SECRET_NAME}"
|
||||
echo "IMPORTANT: back these up securely and delete the Secret when you’re comfortable:"
|
||||
echo " kubectl -n ${VAULT_NS} delete secret ${SECRET_NAME}"
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# verify-gitlab-oidc.sh
|
||||
# Verify GitLab OIDC configuration and connectivity to Authentik
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== GitLab OIDC Configuration Verification ==="
|
||||
echo ""
|
||||
|
||||
# Check if GitLab pods are running
|
||||
echo "1. Checking GitLab pods status..."
|
||||
kubectl get pods -n gitlab -l app=webservice
|
||||
echo ""
|
||||
|
||||
# Check hostAliases in webservice pods
|
||||
echo "2. Verifying hostAliases in GitLab webservice..."
|
||||
WEBSERVICE_POD=$(kubectl get pod -n gitlab -l app=webservice -o jsonpath='{.items[0].metadata.name}')
|
||||
echo "Using pod: $WEBSERVICE_POD"
|
||||
kubectl get pod -n gitlab "$WEBSERVICE_POD" -o jsonpath='{.spec.hostAliases}' | jq .
|
||||
echo ""
|
||||
|
||||
# Test DNS resolution from inside GitLab pod
|
||||
echo "3. Testing DNS resolution for auth.kube.huskypup.net from GitLab pod..."
|
||||
kubectl exec -n gitlab "$WEBSERVICE_POD" -c webservice -- getent hosts auth.kube.huskypup.net || echo "getent not available, trying nslookup..."
|
||||
echo ""
|
||||
|
||||
# Test HTTPS connectivity to Authentik
|
||||
echo "4. Testing HTTPS connectivity to Authentik..."
|
||||
kubectl exec -n gitlab "$WEBSERVICE_POD" -c webservice -- curl -I https://auth.kube.huskypup.net/.well-known/openid-configuration 2>&1 | head -20
|
||||
echo ""
|
||||
|
||||
# Check OIDC configuration
|
||||
echo "5. Checking OIDC discovery endpoint..."
|
||||
kubectl exec -n gitlab "$WEBSERVICE_POD" -c webservice -- curl -s https://auth.kube.huskypup.net/application/o/gitlab/.well-known/openid-configuration 2>&1 | head -10
|
||||
echo ""
|
||||
|
||||
# Check for any SSL errors in logs
|
||||
echo "6. Checking recent GitLab logs for SSL errors..."
|
||||
kubectl logs -n gitlab "$WEBSERVICE_POD" -c webservice --tail=50 | grep -i "ssl\|certificate\|openid" || echo "No SSL/certificate errors found in recent logs"
|
||||
echo ""
|
||||
|
||||
echo "=== Verification Complete ==="
|
||||
echo ""
|
||||
echo "If you see 'HTTP/1.1 200 OK' or 'HTTP/2 200' above, OIDC is working correctly!"
|
||||
echo "If you see SSL errors, check the hostAliases configuration."
|
||||
Executable
+177
@@ -0,0 +1,177 @@
|
||||
#!/bin/bash
|
||||
# Verify GitLab Redis password rotation automation is working
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "===================================================================="
|
||||
echo "GitLab Redis Password Rotation Automation - Verification Script"
|
||||
echo "===================================================================="
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
check_pass() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
check_fail() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
}
|
||||
|
||||
check_warn() {
|
||||
echo -e "${YELLOW}⚠${NC} $1"
|
||||
}
|
||||
|
||||
ERRORS=0
|
||||
|
||||
# Check 1: CronJob exists
|
||||
echo "Checking CronJob deployment..."
|
||||
if kubectl get cronjob -n gitlab redis-secret-monitor >/dev/null 2>&1; then
|
||||
check_pass "CronJob 'redis-secret-monitor' is deployed"
|
||||
|
||||
# Get schedule
|
||||
SCHEDULE=$(kubectl get cronjob -n gitlab redis-secret-monitor -o jsonpath='{.spec.schedule}')
|
||||
echo " Schedule: $SCHEDULE"
|
||||
|
||||
# Get last successful run
|
||||
LAST_SUCCESS=$(kubectl get cronjob -n gitlab redis-secret-monitor -o jsonpath='{.status.lastSuccessfulTime}' 2>/dev/null || echo "Never")
|
||||
echo " Last successful run: $LAST_SUCCESS"
|
||||
else
|
||||
check_fail "CronJob 'redis-secret-monitor' not found"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 2: RBAC resources exist
|
||||
echo "Checking RBAC resources..."
|
||||
if kubectl get serviceaccount -n gitlab redis-restart-sa >/dev/null 2>&1; then
|
||||
check_pass "ServiceAccount 'redis-restart-sa' exists"
|
||||
else
|
||||
check_fail "ServiceAccount 'redis-restart-sa' not found"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
if kubectl get role -n gitlab redis-restart-role >/dev/null 2>&1; then
|
||||
check_pass "Role 'redis-restart-role' exists"
|
||||
else
|
||||
check_fail "Role 'redis-restart-role' not found"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
if kubectl get rolebinding -n gitlab redis-restart-binding >/dev/null 2>&1; then
|
||||
check_pass "RoleBinding 'redis-restart-binding' exists"
|
||||
else
|
||||
check_fail "RoleBinding 'redis-restart-binding' not found"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 3: StatefulSet has tracking annotation
|
||||
echo "Checking StatefulSet annotations..."
|
||||
SECRET_VERSION=$(kubectl get statefulset -n gitlab redis-gitlab -o jsonpath='{.spec.template.metadata.annotations.secret-version/redis-password}' 2>/dev/null || echo "")
|
||||
if [ -n "$SECRET_VERSION" ]; then
|
||||
check_pass "StatefulSet has tracking annotation"
|
||||
echo " Tracked secret version: $SECRET_VERSION"
|
||||
else
|
||||
check_warn "StatefulSet missing tracking annotation (will be added on first CronJob run)"
|
||||
fi
|
||||
|
||||
RESTART_TIME=$(kubectl get statefulset -n gitlab redis-gitlab -o jsonpath='{.spec.template.metadata.annotations.restarted-at}' 2>/dev/null || echo "")
|
||||
if [ -n "$RESTART_TIME" ]; then
|
||||
echo " Last restart: $RESTART_TIME"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 4: Redis is running
|
||||
echo "Checking Redis pod status..."
|
||||
if kubectl get pod -n gitlab redis-gitlab-0 >/dev/null 2>&1; then
|
||||
REDIS_STATUS=$(kubectl get pod -n gitlab redis-gitlab-0 -o jsonpath='{.status.phase}')
|
||||
if [ "$REDIS_STATUS" = "Running" ]; then
|
||||
check_pass "Redis pod is Running"
|
||||
else
|
||||
check_fail "Redis pod status: $REDIS_STATUS"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
else
|
||||
check_fail "Redis pod 'redis-gitlab-0' not found"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 5: Redis authentication works
|
||||
echo "Testing Redis authentication..."
|
||||
if RESULT=$(kubectl exec -n gitlab redis-gitlab-0 -- redis-cli -a "$(kubectl get secret -n gitlab redis-gitlab-secret -o jsonpath='{.data.password}' | base64 -d)" ping 2>&1 | grep PONG); then
|
||||
check_pass "Redis authentication successful (PONG)"
|
||||
else
|
||||
check_fail "Redis authentication failed"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 6: GitLab Sidekiq is healthy
|
||||
echo "Checking GitLab Sidekiq status..."
|
||||
SIDEKIQ_READY=$(kubectl get pods -n gitlab -l app=sidekiq -o jsonpath='{.items[*].status.containerStatuses[*].ready}' | grep -o "true" | wc -l)
|
||||
SIDEKIQ_TOTAL=$(kubectl get pods -n gitlab -l app=sidekiq --no-headers | wc -l)
|
||||
|
||||
if [ "$SIDEKIQ_READY" -eq "$SIDEKIQ_TOTAL" ] && [ "$SIDEKIQ_TOTAL" -gt 0 ]; then
|
||||
check_pass "Sidekiq pods are healthy ($SIDEKIQ_READY/$SIDEKIQ_TOTAL ready)"
|
||||
else
|
||||
check_warn "Sidekiq pods: $SIDEKIQ_READY/$SIDEKIQ_TOTAL ready"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 7: Recent job executions
|
||||
echo "Checking recent CronJob executions..."
|
||||
JOB_COUNT=$(kubectl get jobs -n gitlab -l app=redis-secret-monitor --no-headers 2>/dev/null | wc -l)
|
||||
if [ "$JOB_COUNT" -gt 0 ]; then
|
||||
check_pass "Found $JOB_COUNT recent job execution(s)"
|
||||
|
||||
# Show last job
|
||||
LAST_JOB=$(kubectl get jobs -n gitlab -l app=redis-secret-monitor --sort-by=.metadata.creationTimestamp -o name 2>/dev/null | tail -1)
|
||||
if [ -n "$LAST_JOB" ]; then
|
||||
echo ""
|
||||
echo " Last job logs:"
|
||||
kubectl logs -n gitlab "$LAST_JOB" 2>/dev/null | sed 's/^/ /'
|
||||
fi
|
||||
else
|
||||
check_warn "No recent job executions (CronJob may not have run yet)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 8: Secret version comparison
|
||||
echo "Checking secret version synchronization..."
|
||||
CURRENT_SECRET_VERSION=$(kubectl get secret -n gitlab redis-gitlab-secret -o jsonpath='{.metadata.resourceVersion}')
|
||||
TRACKED_SECRET_VERSION=$(kubectl get statefulset -n gitlab redis-gitlab -o jsonpath='{.spec.template.metadata.annotations.secret-version/redis-password}' 2>/dev/null || echo "")
|
||||
|
||||
echo " Current secret version: $CURRENT_SECRET_VERSION"
|
||||
echo " Tracked version in StatefulSet: $TRACKED_SECRET_VERSION"
|
||||
|
||||
if [ "$CURRENT_SECRET_VERSION" = "$TRACKED_SECRET_VERSION" ]; then
|
||||
check_pass "Secret versions are synchronized"
|
||||
elif [ -z "$TRACKED_SECRET_VERSION" ]; then
|
||||
check_warn "StatefulSet not yet tracking secret version (first sync pending)"
|
||||
else
|
||||
check_warn "Secret versions differ - restart pending on next CronJob run"
|
||||
echo " Next scheduled run will synchronize versions"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo "===================================================================="
|
||||
if [ $ERRORS -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All checks passed!${NC}"
|
||||
echo ""
|
||||
echo "Redis password rotation automation is properly configured."
|
||||
echo "The system will automatically restart Redis when passwords rotate."
|
||||
else
|
||||
echo -e "${RED}✗ $ERRORS check(s) failed${NC}"
|
||||
echo ""
|
||||
echo "Please review the errors above and ensure all automation"
|
||||
echo "components are properly deployed."
|
||||
exit 1
|
||||
fi
|
||||
echo "===================================================================="
|
||||
Reference in New Issue
Block a user