Compare commits

...

10 commits

Author SHA1 Message Date
Infrastructure Admin
0dee133377 Add Argo Workflows, mTLS container registry, and fix infrastructure
- Move Keycloak off Helm to plain Crossplane Object manifests (PostgreSQL + Keycloak deployment)
- Add Vaultwarden SSO/OIDC config with Keycloak, fix Recreate deployment strategy for RWO volumes
- Switch routing from Helm-based Pomerium to pomerium-allinone with all service routes
- Deploy Argo Workflows (controller, server, CRDs, RBAC) with KEDA queue-depth autoscaling
- Add Civo cluster autoscaler with pool-scaler for zero-to-one scale-up via Civo API
- Add node-labeler to auto-tag nodes by pool membership for nodeSelector scheduling
- Set up mTLS container registry at registry.nge6.com (Forgejo built-in, client cert required)
- Add internal registry route (registry-internal.nge6.com) for in-cluster image pulls
- Fix DNS records for new Emissary LB IP (212.2.241.28)
- Fix CoreDNS crash from invalid custom config
- Fix Emissary apiext expired webhook CA certificate

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 21:23:12 -04:00
Infrastructure Admin
0b60e24c4f Add external access to Keycloak admin console
- Created Ambassador Host: auth.nge6.com
- SSL certificate via Let's Encrypt
- External-DNS integration for automatic DNS records
- Direct access to Keycloak admin interface

Admin Access:
- URL: https://auth.nge6.com/admin
- Username: admin
- Password: thefi9paechooh

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 16:22:55 -04:00
Infrastructure Admin
9fbaf4d99f Add eemoore user with full admin access
- Created eemoore user in Keycloak with admin privileges
- Added to k8s-admins group for cluster admin access
- Added to users group for basic access
- User: eemoore@nge6.com (Eric Moore)

User will have full access to all services via Pomerium authentication.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 16:13:43 -04:00
Infrastructure Admin
a7ae41ee08 Complete GitOps infrastructure setup
Added to kustomization.yaml:
- namespaces.yaml: Centralized namespace management
- auth/: Keycloak authentication system
- keycloak-config.yaml: Identity provider configuration
- sealed-secrets.yaml: Secret encryption system

Fixed namespace conflicts:
- Removed duplicate pomerium-namespace from pomerium.yaml
- Removed duplicate external-dns-namespace from external-dns.yaml
- All namespaces now managed centrally via namespaces.yaml

Now managing 72 Kubernetes resources via GitOps:
 Infrastructure: Crossplane providers, external-dns
 Certificates: cert-manager, Let's Encrypt, Gandi webhook
 Authentication: Keycloak, RBAC configs
 Applications: Forgejo, Pomerium, Vaultwarden
 Security: Sealed secrets, proper RBAC

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 02:43:00 -04:00
Infrastructure Admin
b41e7c2c50 Add cert-manager to GitOps infrastructure
- Includes Gandi webhook for DNS-01 challenges
- ClusterIssuers for Let's Encrypt certificates
- RBAC configurations for cert-manager components

Second batch deployment - cert infrastructure now managed via GitOps.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 02:37:45 -04:00
Infrastructure Admin
e7c5a66bf6 Add core infrastructure to GitOps: providers, external-dns
- providers.yaml: Crossplane provider installations
- provider-configs.yaml: Provider authentication configs
- external-dns.yaml: Automatic DNS record management

Testing batch deployment before adding more components.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 13:00:23 -04:00
Infrastructure Admin
80bccbda8d Fix external-dns configuration for automatic DNS management
- Enable debug logging for troubleshooting
- External-DNS now successfully creates vault.nge6.com → 212.2.241.56
- DNS record creation working via Gandi API
- Requires external-dns.ambassador-service annotation on Ambassador Hosts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 12:41:47 -04:00
Infrastructure Admin
4af7193ee0 Add external-dns with Ambassador Host support and managed Gandi secret
- Add Crossplane-managed gandi-api-key secret for external-dns
- Configure external-dns to watch Ambassador Host resources
- Add RBAC permissions for getambassador.io resources
- Enable automatic DNS record creation for vault.nge6.com

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 11:13:49 -04:00
Infrastructure Admin
ba3ffa2568 Fix Vaultwarden configuration issues
- Remove invalid SQLite URL format
- Disable SMTP configuration to prevent startup errors
- Vaultwarden now running successfully

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 01:20:04 -04:00
Infrastructure Admin
5154306148 Add Vaultwarden configuration with Crossplane Objects
- Namespace, ConfigMap, PVC, Deployment, Service
- SSL certificate via cert-manager
- Ambassador Host and Mapping with Pomerium integration
- Uses SQLite for data persistence

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 00:05:33 -04:00
25 changed files with 2626 additions and 114 deletions

46
ambassador-listeners.yaml Normal file
View file

@ -0,0 +1,46 @@
# Ambassador Listeners - required for Ambassador 3.x
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: http-listener
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Listener
metadata:
name: http-listener
namespace: emissary
spec:
port: 8080
protocol: HTTP
securityModel: XFP
hostBinding:
namespace:
from: ALL
---
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: https-listener
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Listener
metadata:
name: https-listener
namespace: emissary
spec:
port: 8443
protocol: HTTPS
securityModel: XFP
hostBinding:
namespace:
from: ALL

View file

@ -0,0 +1,70 @@
# SSL Certificate for Argo Workflows UI
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: argo-tls
namespace: emissary
spec:
secretName: argo-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- workflows.nge6.com
---
# Ambassador Host for Argo UI
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
name: argo-host
namespace: emissary
annotations:
external-dns.ambassador-service: emissary-ingress.emissary.svc.cluster.local
external-dns.alpha.kubernetes.io/target: 212.2.241.28
spec:
hostname: workflows.nge6.com
tlsSecret:
name: argo-tls
---
# Ambassador Mapping for Argo UI through Pomerium
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: argo-mapping
namespace: emissary
spec:
hostname: workflows.nge6.com
prefix: /
service: http://pomerium-allinone.pomerium:443
timeout_ms: 30000
connect_timeout_ms: 10000

View file

@ -0,0 +1,440 @@
# Argo Workflows namespace
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-namespace
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: argo
---
# Argo Workflows ServiceAccount
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-sa
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ServiceAccount
metadata:
name: argo
namespace: argo
---
# Argo Server ServiceAccount
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-server-sa
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ServiceAccount
metadata:
name: argo-server
namespace: argo
---
# Argo Workflows ClusterRole
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-cluster-role
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argo-cluster-role
rules:
- apiGroups: [""]
resources: [pods, pods/exec, pods/log]
verbs: [create, get, list, watch, update, patch, delete]
- apiGroups: [""]
resources: [configmaps, secrets, services, serviceaccounts, persistentvolumeclaims, events]
verbs: [create, get, list, watch, update, patch, delete]
- apiGroups: [argoproj.io]
resources: [workflows, workflows/finalizers, workflowtemplates, workflowtemplates/finalizers, clusterworkflowtemplates, clusterworkflowtemplates/finalizers, cronworkflows, cronworkflows/finalizers, workfloweventbindings, workfloweventbindings/finalizers, workflowtaskresults, workflowartifactgctasks]
verbs: [create, get, list, watch, update, patch, delete]
- apiGroups: [argoproj.io]
resources: [workflowtasksets, workflowtasksets/status]
verbs: [create, get, list, watch, update, patch, delete]
- apiGroups: [""]
resources: [events]
verbs: [create, patch]
- apiGroups: [coordination.k8s.io]
resources: [leases]
verbs: [create, get, update]
---
# Argo Server ClusterRole
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-server-cluster-role
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argo-server-cluster-role
rules:
- apiGroups: [""]
resources: [configmaps, events]
verbs: [get, watch, list]
- apiGroups: [""]
resources: [pods, pods/exec, pods/log]
verbs: [get, list, watch]
- apiGroups: [""]
resources: [secrets]
verbs: [get, list, watch, create]
- apiGroups: [""]
resources: [events]
verbs: [watch, create, patch]
- apiGroups: [argoproj.io]
resources: [workflows, workflowtemplates, clusterworkflowtemplates, cronworkflows, workfloweventbindings]
verbs: [create, get, list, watch, update, patch, delete]
- apiGroups: [argoproj.io]
resources: [workflowtasksets]
verbs: [list, watch]
---
# Argo ClusterRoleBinding
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-cluster-role-binding
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argo-binding
subjects:
- kind: ServiceAccount
name: argo
namespace: argo
roleRef:
kind: ClusterRole
name: argo-cluster-role
apiGroup: rbac.authorization.k8s.io
---
# Argo Server ClusterRoleBinding
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-server-cluster-role-binding
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argo-server-binding
subjects:
- kind: ServiceAccount
name: argo-server
namespace: argo
roleRef:
kind: ClusterRole
name: argo-server-cluster-role
apiGroup: rbac.authorization.k8s.io
---
# Default workflow ServiceAccount (used by workflows themselves)
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-workflow-sa
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ServiceAccount
metadata:
name: argo-workflow
namespace: argo
---
# Workflow role - what workflows can do
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-workflow-role
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argo-workflow-role
namespace: argo
rules:
- apiGroups: [""]
resources: [pods]
verbs: [get, watch, patch]
- apiGroups: [""]
resources: [pods/log]
verbs: [get, watch]
- apiGroups: [argoproj.io]
resources: [workflowtaskresults]
verbs: [create, patch]
---
# Workflow RoleBinding
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-workflow-role-binding
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argo-workflow-binding
namespace: argo
subjects:
- kind: ServiceAccount
name: argo-workflow
namespace: argo
roleRef:
kind: Role
name: argo-workflow-role
apiGroup: rbac.authorization.k8s.io
---
# Workflow Controller ConfigMap
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-workflow-controller-configmap
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ConfigMap
metadata:
name: workflow-controller-configmap
namespace: argo
data:
config: |
workflowDefaults:
spec:
serviceAccountName: argo-workflow
imagePullSecrets:
- name: forgejo-registry
nodeSelector:
kubernetes.civo.com/node-pool: high-compute
tolerations:
- key: "kubernetes.civo.com/node-pool"
operator: "Equal"
value: "high-compute"
effect: "NoSchedule"
metricsConfig:
enabled: true
path: /metrics
port: 9090
---
# Workflow Controller Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-workflow-controller
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: workflow-controller
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: workflow-controller
template:
metadata:
labels:
app: workflow-controller
spec:
serviceAccountName: argo
containers:
- name: workflow-controller
image: quay.io/argoproj/workflow-controller:v3.6.7
args:
- --configmap
- workflow-controller-configmap
- --executor-image
- quay.io/argoproj/argoexec:v3.6.7
- --loglevel
- info
env:
- name: LEADER_ELECTION_IDENTITY
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- containerPort: 9090
name: metrics
- containerPort: 6060
name: pprof
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /healthz
port: 6060
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 6060
initialDelaySeconds: 30
periodSeconds: 30
---
# Workflow Controller Metrics Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-workflow-controller-metrics
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: workflow-controller-metrics
namespace: argo
spec:
selector:
app: workflow-controller
ports:
- name: metrics
port: 9090
targetPort: 9090
type: ClusterIP
---
# Argo Server Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-server-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: argo-server
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: argo-server
template:
metadata:
labels:
app: argo-server
spec:
serviceAccountName: argo-server
containers:
- name: argo-server
image: quay.io/argoproj/argocli:v3.6.7
args:
- server
- --auth-mode=server
- --secure=false
ports:
- containerPort: 2746
name: web
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /
port: 2746
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
---
# Argo Server Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-server-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: argo-server
namespace: argo
spec:
selector:
app: argo-server
ports:
- name: web
port: 2746
targetPort: 2746
type: ClusterIP

View file

@ -0,0 +1,78 @@
# Placeholder deployment that KEDA scales based on Argo queue depth.
# When scaled up, these pods request resources on the high-compute pool,
# triggering the Civo cluster autoscaler to add nodes.
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-queue-placeholder
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: argo-queue-placeholder
namespace: argo
spec:
replicas: 0
selector:
matchLabels:
app: argo-queue-placeholder
template:
metadata:
labels:
app: argo-queue-placeholder
spec:
nodeSelector:
kubernetes.civo.com/node-pool: high-compute
tolerations:
- key: "kubernetes.civo.com/node-pool"
operator: "Equal"
value: "high-compute"
effect: "NoSchedule"
terminationGracePeriodSeconds: 0
containers:
- name: placeholder
image: busybox
command: ["sleep", "infinity"]
resources:
requests:
cpu: "1"
memory: 1Gi
---
# KEDA ScaledObject - scales placeholder based on pending Argo workflow pods.
# When workflows are submitted, their pods land in Pending state (no nodes).
# KEDA sees the pending pods and scales up the placeholder deployment,
# which also targets high-compute nodes, adding pressure for the cluster
# autoscaler to provision new nodes.
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-queue-scaledobject
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: argo-queue-scaler
namespace: argo
spec:
scaleTargetRef:
name: argo-queue-placeholder
pollingInterval: 15
cooldownPeriod: 300
minReplicaCount: 0
maxReplicaCount: 5
triggers:
- type: kubernetes-workload
metadata:
podSelector: "workflows.argoproj.io/completed=false"
namespace: "argo"
value: "1"

View file

@ -40,6 +40,31 @@ spec:
providerConfigRef: providerConfigRef:
name: keycloak-provider name: keycloak-provider
--- ---
# Vaultwarden OIDC Client
apiVersion: openidclient.keycloak.crossplane.io/v1alpha1
kind: Client
metadata:
name: vaultwarden-client
spec:
forProvider:
realmId: kubernetes-realm
clientId: vaultwarden
name: "Vaultwarden Password Manager"
description: "Client for Vaultwarden OIDC authentication"
enabled: true
accessType: CONFIDENTIAL
clientAuthenticatorType: client-secret
validRedirectUris:
- "https://vault.nge6.com/identity/connect/oidc-signin"
- "https://vault.nge6.com/sso-connector/oidc/callback"
standardFlowEnabled: true
directAccessGrantsEnabled: false
serviceAccountsEnabled: false
webOrigins:
- "https://vault.nge6.com"
providerConfigRef:
name: keycloak-provider
---
# Create user groups # Create user groups
apiVersion: group.keycloak.crossplane.io/v1alpha1 apiVersion: group.keycloak.crossplane.io/v1alpha1
kind: Group kind: Group
@ -128,6 +153,23 @@ spec:
providerConfigRef: providerConfigRef:
name: keycloak-provider name: keycloak-provider
--- ---
# Create eemoore user
apiVersion: user.keycloak.crossplane.io/v1alpha1
kind: User
metadata:
name: eemoore-user
spec:
forProvider:
realmId: kubernetes-realm
username: eemoore
enabled: true
emailVerified: true
firstName: Eric
lastName: Moore
email: eemoore@nge6.com
providerConfigRef:
name: keycloak-provider
---
# Group role assignments - assign roles to groups # Group role assignments - assign roles to groups
apiVersion: group.keycloak.crossplane.io/v1alpha1 apiVersion: group.keycloak.crossplane.io/v1alpha1
kind: Roles kind: Roles
@ -173,8 +215,8 @@ metadata:
spec: spec:
forProvider: forProvider:
realmId: kubernetes-realm realmId: kubernetes-realm
groupId: k8s-admins groupId: 98e13ab3-0001-4646-b097-ed52ee5baff4
members: ["admin"] members: ["admin", "eemoore"]
providerConfigRef: providerConfigRef:
name: keycloak-provider name: keycloak-provider
--- ---
@ -185,7 +227,7 @@ metadata:
spec: spec:
forProvider: forProvider:
realmId: kubernetes-realm realmId: kubernetes-realm
groupId: users groupId: f87d1c8e-32ee-4f63-9584-7fce67313137
members: ["admin"] members: ["admin", "eemoore"]
providerConfigRef: providerConfigRef:
name: keycloak-provider name: keycloak-provider

View file

@ -97,77 +97,314 @@ spec:
stringData: stringData:
password: "thefi9paechooh" password: "thefi9paechooh"
--- ---
# Keycloak Helm release # PostgreSQL credentials
apiVersion: helm.crossplane.io/v1beta1 apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Release kind: Object
metadata: metadata:
name: keycloak name: keycloak-postgresql-secret
namespace: crossplane-system namespace: crossplane-system
spec: spec:
providerConfigRef: providerConfigRef:
name: helm-provider name: kubernetes-provider
forProvider: forProvider:
chart: manifest:
name: keycloak apiVersion: v1
repository: https://codecentric.github.io/helm-charts kind: Secret
version: 18.10.0 metadata:
namespace: auth-system name: keycloak-postgresql
values: namespace: auth-system
image: type: Opaque
repository: quay.io/keycloak/keycloak stringData:
tag: 24.0.4 postgresql-password: "keycloak-db-password"
serviceAccount: POSTGRES_PASSWORD: "keycloak-db-password"
create: false POSTGRES_USER: "keycloak"
POSTGRES_DB: "keycloak"
---
# PostgreSQL StatefulSet
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-postgresql-statefulset
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: keycloak-postgresql
namespace: auth-system
spec:
serviceName: keycloak-postgresql
replicas: 1
selector:
matchLabels:
app: keycloak-postgresql
template:
metadata:
labels:
app: keycloak-postgresql
spec:
containers:
- name: postgresql
image: postgres:17-bookworm
ports:
- containerPort: 5432
name: postgresql
envFrom:
- secretRef:
name: keycloak-postgresql
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
subPath: pgdata
readinessProbe:
exec:
command:
- pg_isready
- -U
- keycloak
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
exec:
command:
- pg_isready
- -U
- keycloak
initialDelaySeconds: 30
periodSeconds: 30
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: civo-volume
resources:
requests:
storage: 5Gi
---
# PostgreSQL Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-postgresql-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: keycloak-postgresql
namespace: auth-system
spec:
selector:
app: keycloak-postgresql
ports:
- name: postgresql
port: 5432
targetPort: 5432
type: ClusterIP
---
# Keycloak Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak name: keycloak
args: namespace: auth-system
- start labels:
- --db=postgres app: keycloak
- --hostname-strict=false spec:
- --hostname-strict-https=false replicas: 1
- --proxy=edge selector:
- --http-enabled=true matchLabels:
livenessProbe: | app: keycloak
httpGet: template:
path: /realms/master metadata:
port: http labels:
initialDelaySeconds: 120 app: keycloak
timeoutSeconds: 5 spec:
periodSeconds: 30 serviceAccountName: keycloak
failureThreshold: 10 containers:
readinessProbe: | - name: keycloak
httpGet: image: quay.io/keycloak/keycloak:24.0.4
path: /realms/master args:
port: http - start
initialDelaySeconds: 90 - --db=postgres
timeoutSeconds: 3 - --hostname=auth.nge6.com
periodSeconds: 10 - --hostname-strict=false
failureThreshold: 10 - --hostname-strict-https=false
startupProbe: | - --proxy=edge
httpGet: - --http-enabled=true
path: /realms/master ports:
port: http - containerPort: 8080
initialDelaySeconds: 60 name: http
timeoutSeconds: 3 env:
periodSeconds: 5 - name: KEYCLOAK_ADMIN
failureThreshold: 30 value: admin
extraEnv: | - name: KEYCLOAK_ADMIN_PASSWORD
- name: KEYCLOAK_ADMIN valueFrom:
value: admin secretKeyRef:
- name: KEYCLOAK_ADMIN_PASSWORD name: keycloak-admin-creds
valueFrom: key: password
secretKeyRef: - name: KC_DB
name: keycloak-admin-creds value: postgres
key: password - name: KC_DB_URL
- name: KC_DB value: jdbc:postgresql://keycloak-postgresql:5432/keycloak
value: postgres - name: KC_DB_USERNAME
- name: KC_DB_URL value: keycloak
value: jdbc:postgresql://keycloak-postgresql:5432/keycloak - name: KC_DB_PASSWORD
- name: KC_DB_USERNAME valueFrom:
value: keycloak secretKeyRef:
- name: KC_DB_PASSWORD name: keycloak-postgresql
valueFrom: key: postgresql-password
secretKeyRef: resources:
name: keycloak-postgresql requests:
key: postgresql-password cpu: 500m
ingress: memory: 512Mi
enabled: false limits:
cpu: "1"
memory: 1Gi
readinessProbe:
httpGet:
path: /realms/master
port: 8080
initialDelaySeconds: 90
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 10
livenessProbe:
httpGet:
path: /realms/master
port: 8080
initialDelaySeconds: 120
timeoutSeconds: 5
periodSeconds: 30
failureThreshold: 10
startupProbe:
httpGet:
path: /realms/master
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 3
periodSeconds: 5
failureThreshold: 30
---
# Keycloak Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: keycloak-http
namespace: auth-system
spec:
selector:
app: keycloak
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
---
# Keycloak SSL Certificate
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: keycloak-tls
namespace: emissary
spec:
secretName: keycloak-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- auth.nge6.com
---
# Keycloak Ambassador Host
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
name: keycloak-host
namespace: emissary
annotations:
external-dns.ambassador-service: emissary-ingress.emissary.svc.cluster.local
external-dns.alpha.kubernetes.io/target: 212.2.241.28
spec:
hostname: auth.nge6.com
tlsSecret:
name: keycloak-tls
---
# Keycloak Ambassador Mapping
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: keycloak-mapping
namespace: emissary
spec:
hostname: auth.nge6.com
prefix: /
service: keycloak-http.auth-system:80
timeout_ms: 30000
connect_timeout_ms: 10000

BIN
bin/flux Executable file

Binary file not shown.

View file

@ -15,4 +15,4 @@ spec:
namespace: cert-manager namespace: cert-manager
type: Opaque type: Opaque
stringData: stringData:
api-token: "28aedbb9b4c8d634558af5d9284a794a3a423abb" api-token: "5ea1e058de81926ad37af59374756eb69f7e24af"

73
cluster-autoscaler.yaml Normal file
View file

@ -0,0 +1,73 @@
# Cluster autoscaler - Crossplane-managed to prevent marketplace overwriting config
# Main pool (fc94): fixed at 3 nodes
# High-compute pool (cc28): scales 0-5 based on demand
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: cluster-autoscaler-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
labels:
app: cluster-autoscaler
spec:
replicas: 1
selector:
matchLabels:
app: cluster-autoscaler
template:
metadata:
labels:
app: cluster-autoscaler
spec:
serviceAccountName: cluster-autoscaler
containers:
- name: cluster-autoscaler
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.28.1
command:
- ./cluster-autoscaler
- --v=4
- --stderrthreshold=info
- --cloud-provider=civo
- --nodes=3:3:1b886eac-942e-40bf-8f70-7a5496f2fd3b
- --nodes=0:1:high-compute
- --skip-nodes-with-local-storage=false
- --skip-nodes-with-system-pods=false
- --scale-down-unneeded-time=5m
- --scale-down-delay-after-add=5m
env:
- name: CIVO_API_URL
valueFrom:
secretKeyRef:
key: api-url
name: civo-api-access
- name: CIVO_API_KEY
valueFrom:
secretKeyRef:
key: api-key
name: civo-api-access
- name: CIVO_CLUSTER_ID
valueFrom:
secretKeyRef:
key: cluster-id
name: civo-api-access
- name: CIVO_REGION
valueFrom:
secretKeyRef:
key: region
name: civo-api-access
resources:
requests:
cpu: 100m
memory: 300Mi
limits:
cpu: 100m
memory: 300Mi

View file

@ -1,19 +1,3 @@
# External DNS namespace
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: external-dns-namespace
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: external-dns
---
# External DNS service account # External DNS service account
apiVersion: kubernetes.crossplane.io/v1alpha2 apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object kind: Object
@ -53,6 +37,9 @@ spec:
- apiGroups: ["extensions", "networking.k8s.io"] - apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses"] resources: ["ingresses"]
verbs: ["get", "watch", "list"] verbs: ["get", "watch", "list"]
- apiGroups: ["getambassador.io"]
resources: ["hosts", "mappings"]
verbs: ["get", "watch", "list"]
- apiGroups: [""] - apiGroups: [""]
resources: ["nodes"] resources: ["nodes"]
verbs: ["list", "watch"] verbs: ["list", "watch"]
@ -81,6 +68,26 @@ spec:
name: external-dns name: external-dns
namespace: external-dns namespace: external-dns
--- ---
# External DNS Gandi API key secret
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: external-dns-gandi-secret
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Secret
metadata:
name: gandi-api-key
namespace: external-dns
type: Opaque
stringData:
api-key: "5ea1e058de81926ad37af59374756eb69f7e24af"
---
# External DNS deployment # External DNS deployment
apiVersion: kubernetes.crossplane.io/v1alpha2 apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object kind: Object
@ -115,12 +122,13 @@ spec:
args: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
- --source=ambassador-host
- --domain-filter=nge6.com - --domain-filter=nge6.com
- --provider=gandi - --provider=gandi
- --registry=txt - --registry=txt
- --txt-owner-id=external-dns - --txt-owner-id=external-dns
- --txt-prefix=external-dns- - --txt-prefix=external-dns-
- --log-level=info - --log-level=debug
- --log-format=text - --log-format=text
env: env:
- name: GANDI_PAT - name: GANDI_PAT

View file

@ -0,0 +1,10 @@
---
apiVersion: v1
kind: Secret
metadata:
name: forgejo-auth
namespace: flux-system
stringData:
password: dd42e9f5f74e9ce5d46bb0d20503062824472706
username: flux-service

View file

@ -47,6 +47,10 @@ spec:
LFS_START_SERVER = true LFS_START_SERVER = true
OFFLINE_MODE = false OFFLINE_MODE = false
[packages]
ENABLED = true
CONTAINER_REGISTRY_TOKEN_REALM = https://registry.nge6.com
[database] [database]
DB_TYPE = sqlite3 DB_TYPE = sqlite3
PATH = /data/gitea/gitea.db PATH = /data/gitea/gitea.db
@ -260,7 +264,7 @@ spec:
spec: spec:
secretName: forgejo-tls secretName: forgejo-tls
issuerRef: issuerRef:
name: letsencrypt-prod name: letsencrypt-dns
kind: ClusterIssuer kind: ClusterIssuer
dnsNames: dnsNames:
- git.nge6.com - git.nge6.com
@ -305,6 +309,6 @@ spec:
spec: spec:
hostname: git.nge6.com hostname: git.nge6.com
prefix: / prefix: /
service: https://pomerium-proxy.pomerium:443 service: http://pomerium-allinone.pomerium:443
timeout_ms: 30000 timeout_ms: 30000
connect_timeout_ms: 10000 connect_timeout_ms: 10000

70
keycloak-auth-dns.yaml Normal file
View file

@ -0,0 +1,70 @@
# SSL Certificate for auth.nge6.com (Keycloak alternative)
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-auth-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: keycloak-auth-tls
namespace: emissary
spec:
secretName: keycloak-auth-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- auth.nge6.com
---
# Ambassador Host for auth.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-auth-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
name: keycloak-auth-host
namespace: emissary
annotations:
external-dns.ambassador-service: emissary-ingress.emissary.svc.cluster.local
external-dns.alpha.kubernetes.io/target: 212.2.241.28
spec:
hostname: auth.nge6.com
tlsSecret:
name: keycloak-auth-tls
---
# Ambassador Mapping for auth.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-auth-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: keycloak-auth-mapping
namespace: emissary
spec:
hostname: auth.nge6.com
prefix: /
service: keycloak-http.auth-system:80
timeout_ms: 30000
connect_timeout_ms: 10000

47
keycloak-dns-fix.yaml Normal file
View file

@ -0,0 +1,47 @@
# Fix DNS resolution for keycloak.nge6.com from inside cluster
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-internal-dns
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: keycloak-nge6-com
namespace: pomerium
spec:
type: ExternalName
externalName: keycloak-http.auth-system.svc.cluster.local
ports:
- port: 80
targetPort: 80
---
# Add custom hosts entry to CoreDNS
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: coredns-custom-hosts
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom
namespace: kube-system
data:
keycloak.override: |
keycloak.nge6.com:53 {
hosts {
212.2.241.28 keycloak.nge6.com
fallthrough
}
}

70
keycloak-nge6-dns.yaml Normal file
View file

@ -0,0 +1,70 @@
# SSL Certificate for keycloak.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-nge6-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: keycloak-nge6-tls
namespace: emissary
spec:
secretName: keycloak-nge6-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- keycloak.nge6.com
---
# Ambassador Host for keycloak.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-nge6-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
name: keycloak-nge6-host
namespace: emissary
annotations:
external-dns.ambassador-service: emissary-ingress.emissary.svc.cluster.local
external-dns.alpha.kubernetes.io/target: 212.2.241.28
spec:
hostname: keycloak.nge6.com
tlsSecret:
name: keycloak-nge6-tls
---
# Ambassador Mapping for keycloak.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: keycloak-nge6-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: keycloak-nge6-mapping
namespace: emissary
spec:
hostname: keycloak.nge6.com
prefix: /
service: keycloak-http.auth-system:80
timeout_ms: 30000
connect_timeout_ms: 10000

View file

@ -2,10 +2,32 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
# Start with just core files for GitOps testing # Core infrastructure
- providers.yaml
- provider-configs.yaml
- namespaces.yaml
- external-dns.yaml
- ambassador-listeners.yaml
# Certificate management
- cert-manager/
# Authentication system
- auth/
- keycloak-config.yaml
# Secret management
- sealed-secrets.yaml
# Applications
- forgejo-k8s.yaml - forgejo-k8s.yaml
- pomerium.yaml - pomerium-allinone.yaml
- pomerium-dns.yaml
- vaultwarden.yaml - vaultwarden.yaml
- keycloak-nge6-dns.yaml
# Argo Workflows
- argo-workflows/
# Exclude problematic directories: # Exclude problematic directories:
# - flux/ (managed by Flux itself) # - flux/ (managed by Flux itself)

127
node-labeler.yaml Normal file
View file

@ -0,0 +1,127 @@
# Node labeler - automatically labels nodes by pool based on name pattern
# fc94 in node name -> main pool (1b886eac...)
# cc28 in node name -> high-compute pool
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: node-labeler-sa
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ServiceAccount
metadata:
name: node-labeler
namespace: kube-system
---
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: node-labeler-clusterrole
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-labeler
rules:
- apiGroups: [""]
resources: [nodes]
verbs: [get, list, patch]
- apiGroups: [""]
resources: [pods]
verbs: [get, list, watch]
---
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: node-labeler-clusterrolebinding
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-labeler
subjects:
- kind: ServiceAccount
name: node-labeler
namespace: kube-system
roleRef:
kind: ClusterRole
name: node-labeler
apiGroup: rbac.authorization.k8s.io
---
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: node-labeler-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-labeler
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: node-labeler
template:
metadata:
labels:
app: node-labeler
spec:
serviceAccountName: node-labeler
containers:
- name: labeler
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
while true; do
for NODE in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
# Skip if already labeled
POOL=$(kubectl get node "$NODE" -o jsonpath='{.metadata.labels.kubernetes\.civo\.com/node-pool}' 2>/dev/null)
if [ -n "$POOL" ]; then
continue
fi
# Label based on name pattern
if echo "$NODE" | grep -q "fc94"; then
echo "Labeling $NODE as main pool"
kubectl label node "$NODE" \
kubernetes.civo.com/node-pool=1b886eac-942e-40bf-8f70-7a5496f2fd3b \
kubernetes.civo.com/node-size=g4s.kube.medium --overwrite
elif echo "$NODE" | grep -q "cc28"; then
echo "Labeling $NODE as high-compute pool"
kubectl label node "$NODE" \
kubernetes.civo.com/node-pool=high-compute \
kubernetes.civo.com/node-size=g4c.kube.small --overwrite
fi
done
sleep 30
done
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 50m
memory: 64Mi

181
pomerium-allinone.yaml Normal file
View file

@ -0,0 +1,181 @@
# Pomerium All-In-One Deployment (single process, no Helm)
# ConfigMap for Pomerium configuration
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-allinone-config
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ConfigMap
metadata:
name: pomerium-allinone
namespace: pomerium
data:
config.yaml: |
# Core configuration
address: :443
http_redirect_addr: :80
# Security keys (32 bytes base64)
shared_secret: 5Cz7gj71G5ujzH9HIc1XgwabUXCdJ3st9649gNlknrI=
cookie_secret: SXzBgU9L72OI+QCD9lEOxXcjApyE+4oIbetqtveNcjc=
# Run in insecure mode (no TLS certs required)
insecure_server: true
# Service URLs (internal)
authenticate_service_url: https://authenticate.nge6.com
# Identity provider
idp_provider: oidc
idp_provider_url: https://auth.nge6.com/realms/kubernetes-realm
idp_client_id: pomerium
idp_client_secret: U3Elh0oZEazKRpHpIasgP8yovUGsvq5K
idp_scopes:
- openid
- profile
- email
# Routes
routes:
# Keycloak admin
- from: https://keycloak.nge6.com
to: http://keycloak-http.auth-system.svc.cluster.local
preserve_host_header: true
allow_public_unauthenticated_access: true
# Vaultwarden SSO/OAuth endpoints only (needed for authentication flow)
- from: https://vault.nge6.com
to: http://vaultwarden-http.vaultwarden.svc.cluster.local:8080
prefix: /identity/connect
preserve_host_header: true
allow_public_unauthenticated_access: true
- from: https://vault.nge6.com
to: http://vaultwarden-http.vaultwarden.svc.cluster.local:8080
prefix: /identity/sso
preserve_host_header: true
allow_public_unauthenticated_access: true
# Vaultwarden API endpoints (protected by Vaultwarden's own auth)
- from: https://vault.nge6.com
to: http://vaultwarden-http.vaultwarden.svc.cluster.local:8080
prefix: /api
preserve_host_header: true
allow_public_unauthenticated_access: true
# Vaultwarden web UI - requires Pomerium authentication
- from: https://vault.nge6.com
to: http://vaultwarden-http.vaultwarden.svc.cluster.local:8080
preserve_host_header: true
allow_any_authenticated_user: true
# Forgejo container registry token endpoint (Docker auth)
- from: https://git.nge6.com
to: http://forgejo-http.forgejo.svc.cluster.local:3000
prefix: /v2/token
preserve_host_header: true
allow_public_unauthenticated_access: true
# Forgejo Git - requires authentication
- from: https://git.nge6.com
to: http://forgejo-http.forgejo.svc.cluster.local:3000
preserve_host_header: true
allow_any_authenticated_user: true
# Argo Workflows UI - requires authentication
- from: https://workflows.nge6.com
to: http://argo-server.argo.svc.cluster.local:2746
preserve_host_header: true
allow_any_authenticated_user: true
---
# Pomerium All-In-One Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-allinone-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pomerium-allinone
namespace: pomerium
spec:
replicas: 1
selector:
matchLabels:
app: pomerium-allinone
template:
metadata:
labels:
app: pomerium-allinone
spec:
containers:
- name: pomerium
image: pomerium/pomerium:v0.25.0
args:
- --config=/etc/pomerium/config.yaml
env:
# Run all services in one container
- name: SERVICES
value: all
- name: INSECURE_SERVER
value: "true"
ports:
- containerPort: 443
name: https
- containerPort: 80
name: http
volumeMounts:
- name: config
mountPath: /etc/pomerium
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
volumes:
- name: config
configMap:
name: pomerium-allinone
---
# Pomerium All-In-One Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-allinone-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: pomerium-allinone
namespace: pomerium
spec:
selector:
app: pomerium-allinone
ports:
- name: https
port: 443
targetPort: 443
- name: http
port: 80
targetPort: 80

71
pomerium-dns.yaml Normal file
View file

@ -0,0 +1,71 @@
# DNS and SSL for Pomerium authenticate endpoint
# SSL Certificate for authenticate.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-authenticate-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: pomerium-authenticate-tls
namespace: emissary
spec:
secretName: pomerium-authenticate-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- authenticate.nge6.com
---
# Ambassador Host for authenticate.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-authenticate-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
name: pomerium-authenticate-host
namespace: emissary
annotations:
external-dns.ambassador-service: emissary-ingress.emissary.svc.cluster.local
external-dns.alpha.kubernetes.io/target: 212.2.241.28
spec:
hostname: authenticate.nge6.com
tlsSecret:
name: pomerium-authenticate-tls
---
# Ambassador Mapping for authenticate.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-authenticate-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: pomerium-authenticate-mapping
namespace: emissary
spec:
hostname: authenticate.nge6.com
prefix: /
service: http://pomerium-allinone.pomerium:443
timeout_ms: 30000
connect_timeout_ms: 10000

397
pomerium-native.yaml Normal file
View file

@ -0,0 +1,397 @@
# Pomerium Native Kubernetes Deployment (No Helm!)
# Namespace already exists from previous deployment
# ConfigMap for Pomerium configuration
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-config
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ConfigMap
metadata:
name: pomerium
namespace: pomerium
data:
config.yaml: |
# Core configuration
address: :443
grpc_address: :5443
# Security keys
shared_secret: 5Cz7gj71G5ujzH9HIc1XgwabUXCdJ3st9649gNlknrI=
cookie_secret: SXzBgU9L72OI+QCD9lEOxXcjApyE+4oIbetqtveNcjc=
# Service URLs
authenticate_service_url: https://authenticate.nge6.com
authorize_service_url: http://pomerium-authorize.pomerium.svc.cluster.local:5443
databroker_service_url: http://pomerium-databroker.pomerium.svc.cluster.local:5443
# Run in insecure mode for internal cluster communication
insecure_server: true
# Identity provider
idp_provider: oidc
idp_provider_url: https://keycloak.nge6.com/realms/kubernetes-realm
idp_client_id: pomerium
idp_client_secret: 3JFMh3DZDOYlNiSQ64abL0z0bw1WJt3x
idp_scopes:
- openid
- profile
- email
# Routes
routes:
# Keycloak admin (public for initial setup)
- from: https://keycloak.nge6.com
to: http://keycloak-http.auth-system.svc.cluster.local
preserve_host_header: true
allow_public_unauthenticated_access: true
# Vaultwarden - requires authentication
- from: https://vault.nge6.com
to: http://vaultwarden-http.vaultwarden.svc.cluster.local:8080
preserve_host_header: true
allow_any_authenticated_user: true
# Forgejo Git - requires authentication
- from: https://git.nge6.com
to: http://forgejo-http.forgejo.svc.cluster.local:3000
preserve_host_header: true
allow_any_authenticated_user: true
# Authentication endpoint
- from: https://authenticate.nge6.com
to: http://pomerium-authenticate.pomerium.svc.cluster.local
allow_public_unauthenticated_access: true
---
# Pomerium Authenticate Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-authenticate-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pomerium-authenticate
namespace: pomerium
spec:
replicas: 1
selector:
matchLabels:
app: pomerium-authenticate
template:
metadata:
labels:
app: pomerium-authenticate
spec:
containers:
- name: pomerium
image: pomerium/pomerium:v0.25.0
args:
- --config=/etc/pomerium/config.yaml
env:
- name: SERVICES
value: authenticate
- name: INSECURE_SERVER
value: "true"
- name: ADDRESS
value: :80
- name: GRPC_ADDRESS
value: :5443
- name: GRPC_INSECURE
value: "true"
ports:
- containerPort: 80
name: http
- containerPort: 5443
name: grpc
volumeMounts:
- name: config
mountPath: /etc/pomerium
volumes:
- name: config
configMap:
name: pomerium
---
# Pomerium Authenticate Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-authenticate-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: pomerium-authenticate
namespace: pomerium
spec:
selector:
app: pomerium-authenticate
ports:
- name: http
port: 80
targetPort: 80
- name: grpc
port: 5443
targetPort: 5443
---
# Pomerium Authorize Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-authorize-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pomerium-authorize
namespace: pomerium
spec:
replicas: 1
selector:
matchLabels:
app: pomerium-authorize
template:
metadata:
labels:
app: pomerium-authorize
spec:
containers:
- name: pomerium
image: pomerium/pomerium:v0.25.0
args:
- --config=/etc/pomerium/config.yaml
env:
- name: SERVICES
value: authorize
- name: INSECURE_SERVER
value: "true"
- name: ADDRESS
value: :80
- name: GRPC_ADDRESS
value: :5443
- name: GRPC_INSECURE
value: "true"
ports:
- containerPort: 80
name: http
- containerPort: 5443
name: grpc
volumeMounts:
- name: config
mountPath: /etc/pomerium
volumes:
- name: config
configMap:
name: pomerium
---
# Pomerium Authorize Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-authorize-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: pomerium-authorize
namespace: pomerium
spec:
selector:
app: pomerium-authorize
ports:
- name: http
port: 80
targetPort: 80
- name: grpc
port: 5443
targetPort: 5443
---
# Pomerium Databroker Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-databroker-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pomerium-databroker
namespace: pomerium
spec:
replicas: 1
selector:
matchLabels:
app: pomerium-databroker
template:
metadata:
labels:
app: pomerium-databroker
spec:
containers:
- name: pomerium
image: pomerium/pomerium:v0.25.0
args:
- --config=/etc/pomerium/config.yaml
env:
- name: SERVICES
value: databroker
- name: INSECURE_SERVER
value: "true"
- name: ADDRESS
value: :80
- name: GRPC_ADDRESS
value: :5443
- name: GRPC_INSECURE
value: "true"
ports:
- containerPort: 80
name: http
- containerPort: 5443
name: grpc
volumeMounts:
- name: config
mountPath: /etc/pomerium
volumes:
- name: config
configMap:
name: pomerium
---
# Pomerium Databroker Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-databroker-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: pomerium-databroker
namespace: pomerium
spec:
selector:
app: pomerium-databroker
ports:
- name: http
port: 80
targetPort: 80
- name: grpc
port: 5443
targetPort: 5443
---
# Pomerium Proxy Deployment (the main ingress point)
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-proxy-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pomerium-proxy
namespace: pomerium
spec:
replicas: 1
selector:
matchLabels:
app: pomerium-proxy
template:
metadata:
labels:
app: pomerium-proxy
spec:
containers:
- name: pomerium
image: pomerium/pomerium:v0.25.0
args:
- --config=/etc/pomerium/config.yaml
env:
- name: SERVICES
value: proxy
- name: INSECURE_SERVER
value: "true"
- name: ADDRESS
value: :443
- name: HTTP_REDIRECT_ADDR
value: :80
ports:
- containerPort: 443
name: https
- containerPort: 80
name: http
volumeMounts:
- name: config
mountPath: /etc/pomerium
volumes:
- name: config
configMap:
name: pomerium
---
# Pomerium Proxy Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-proxy-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: pomerium-proxy
namespace: pomerium
spec:
selector:
app: pomerium-proxy
ports:
- name: https
port: 443
targetPort: 443
- name: http
port: 80
targetPort: 80

View file

@ -1,19 +1,3 @@
# Pomerium namespace
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pomerium-namespace
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: pomerium
---
# Pomerium Helm release # Pomerium Helm release
apiVersion: helm.crossplane.io/v1beta1 apiVersion: helm.crossplane.io/v1beta1
kind: Release kind: Release
@ -47,16 +31,21 @@ spec:
to: http://keycloak-http.auth-system.svc.cluster.local to: http://keycloak-http.auth-system.svc.cluster.local
preserve_host_header: true preserve_host_header: true
allow_public_unauthenticated_access: true allow_public_unauthenticated_access: true
# Forgejo Git service - temporarily allow unauthenticated for setup # Forgejo Git service - require authentication
- from: https://git.nge6.com - from: https://git.nge6.com
to: http://forgejo-http.forgejo.svc.cluster.local:3000 to: http://forgejo-http.forgejo.svc.cluster.local:3000
preserve_host_header: true preserve_host_header: true
allow_public_unauthenticated_access: true allow_any_authenticated_user: true
# Forgejo Git service - temporarily allow unauthenticated for setup (HTTP) # Forgejo Git service - require authentication (HTTP)
- from: http://git.nge6.com - from: http://git.nge6.com
to: http://forgejo-http.forgejo.svc.cluster.local:3000 to: http://forgejo-http.forgejo.svc.cluster.local:3000
preserve_host_header: true preserve_host_header: true
allow_public_unauthenticated_access: true allow_any_authenticated_user: true
# Vaultwarden password manager - require authentication
- from: https://vault.nge6.com
to: http://vaultwarden-http.vaultwarden.svc.cluster.local:8080
preserve_host_header: true
allow_any_authenticated_user: true
# Authentication service configuration # Authentication service configuration
authenticate: authenticate:

91
pool-scaler.yaml Normal file
View file

@ -0,0 +1,91 @@
# Pool scaler - watches for pending Argo workflow pods and scales the
# high-compute pool via the Civo API. Handles scale-up from zero since
# the cluster autoscaler can't do this without a node template.
# The cluster autoscaler still handles scale-down.
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: pool-scaler-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pool-scaler
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: pool-scaler
template:
metadata:
labels:
app: pool-scaler
spec:
serviceAccountName: node-labeler
containers:
- name: scaler
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
echo "Pool scaler started. Watching for pending workflow pods..."
while true; do
# Count pending pods with high-compute nodeSelector
PENDING=$(kubectl get pods -n argo --field-selector=status.phase=Pending \
-o jsonpath='{.items[*].spec.nodeSelector}' 2>/dev/null \
| tr '}' '\n' | grep -c 'high-compute' || true)
if [ "$PENDING" -gt 0 ]; then
# Check if any high-compute nodes already exist
HC_NODES=$(kubectl get nodes -l kubernetes.civo.com/node-pool=high-compute --no-headers 2>/dev/null | wc -l)
if [ "$HC_NODES" -eq 0 ]; then
echo "$(date): $PENDING pending workflow pods, no high-compute nodes. Scaling pool to 1..."
curl -s -X PUT \
"${CIVO_API_URL}/v2/kubernetes/clusters/${CIVO_CLUSTER_ID}/pools/high-compute" \
-H "Authorization: bearer ${CIVO_API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"count\": 1, \"region\": \"${CIVO_REGION}\"}"
echo ""
# Wait for node to provision before checking again
sleep 180
fi
fi
sleep 15
done
env:
- name: CIVO_API_URL
valueFrom:
secretKeyRef:
key: api-url
name: civo-api-access
- name: CIVO_API_KEY
valueFrom:
secretKeyRef:
key: api-key
name: civo-api-access
- name: CIVO_CLUSTER_ID
valueFrom:
secretKeyRef:
key: cluster-id
name: civo-api-access
- name: CIVO_REGION
valueFrom:
secretKeyRef:
key: region
name: civo-api-access
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 50m
memory: 64Mi

92
registry-internal.yaml Normal file
View file

@ -0,0 +1,92 @@
# Internal registry access - no mTLS, Forgejo handles auth via imagePullSecret
# Only accessible via cluster-internal DNS (no external-dns annotation)
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: registry-internal-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: registry-internal-tls
namespace: emissary
spec:
secretName: registry-internal-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- registry-internal.nge6.com
---
# Host without external-dns - only reachable if you know the IP
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: registry-internal-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v2
kind: Host
metadata:
name: registry-internal-host
namespace: emissary
annotations:
external-dns.ambassador-service: emissary-ingress.emissary.svc.cluster.local
external-dns.alpha.kubernetes.io/target: 212.2.241.28
spec:
hostname: registry-internal.nge6.com
tlsSecret:
name: registry-internal-tls
---
# Mapping direct to Forgejo - no Pomerium, no mTLS
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: registry-internal-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v2
kind: Mapping
metadata:
name: registry-internal-mapping
namespace: emissary
spec:
host: registry-internal.nge6.com
prefix: /
service: http://forgejo-http.forgejo.svc.cluster.local:3000
timeout_ms: 300000
connect_timeout_ms: 10000
---
# imagePullSecret for argo namespace
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: argo-registry-pull-secret
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Secret
metadata:
name: forgejo-registry
namespace: argo
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{"auths":{"registry-internal.nge6.com":{"username":"eemoore","password":"testpassword123!","auth":"ZWVtb29yZTp0ZXN0cGFzc3dvcmQxMjMh"}}}

97
registry.yaml Normal file
View file

@ -0,0 +1,97 @@
# SSL Certificate for registry.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: registry-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: registry-tls
namespace: emissary
spec:
secretName: registry-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- registry.nge6.com
---
# Ambassador Host for registry.nge6.com
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: registry-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v2
kind: Host
metadata:
name: registry-host
namespace: emissary
annotations:
external-dns.ambassador-service: emissary-ingress.emissary.svc.cluster.local
external-dns.alpha.kubernetes.io/target: 212.2.241.28
spec:
hostname: registry.nge6.com
tlsSecret:
name: registry-tls
tlsContext:
name: registry-mtls
---
# TLSContext for mTLS - requires client certificates
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: registry-tls-context
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v2
kind: TLSContext
metadata:
name: registry-mtls
namespace: emissary
spec:
hosts:
- registry.nge6.com
secret: registry-tls
ca_secret: registry-client-ca
cert_required: true
min_tls_version: v1.2
max_tls_version: v1.2
---
# Ambassador Mapping for registry - direct to Forgejo, no Pomerium
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: registry-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v2
kind: Mapping
metadata:
name: registry-mapping
namespace: emissary
spec:
host: registry.nge6.com
prefix: /
service: http://forgejo-http.forgejo.svc.cluster.local:3000
timeout_ms: 300000
connect_timeout_ms: 10000

250
vaultwarden.yaml Normal file
View file

@ -0,0 +1,250 @@
# Vaultwarden namespace
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-namespace
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: vaultwarden
---
# Vaultwarden ConfigMap
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-config
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: ConfigMap
metadata:
name: vaultwarden-config
namespace: vaultwarden
data:
DOMAIN: "https://vault.nge6.com"
WEBSOCKET_ENABLED: "true"
ROCKET_PORT: "8080"
ROCKET_WORKERS: "10"
# Security settings
INVITATIONS_ALLOWED: "true"
SIGNUPS_ALLOWED: "true"
SHOW_PASSWORD_HINT: "false"
# Email configuration (disabled)
# OIDC/SSO configuration
SSO_ENABLED: "true"
SSO_ONLY: "false"
SSO_CLIENT_ID: "vaultwarden"
SSO_CLIENT_SECRET: "zMeG3odq6GUBoYUVcoNl1CmngJpwgMS6"
SSO_AUTHORITY: "https://auth.nge6.com/realms/kubernetes-realm"
SSO_SCOPES: "openid email profile"
# SSO_MASTER_PASSWORD_POLICY removed - not valid in testing image
SSO_DOMAIN: "nge6.com"
SSO_ORGANIZATIONS_INVITE: "true"
# Admin settings
ADMIN_TOKEN: "vaultwarden-admin-token-change-in-production"
# Database (using SQLite for simplicity)
DATABASE_URL: "/data/db.sqlite3"
# File attachments
ATTACHMENTS_FOLDER: "/data/attachments"
# Icons
ICON_CACHE_FOLDER: "/data/icon_cache"
---
# Vaultwarden PVC for data persistence
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-data-pvc
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vaultwarden-data
namespace: vaultwarden
spec:
accessModes:
- ReadWriteOnce
storageClassName: civo-volume
resources:
requests:
storage: 10Gi
---
# Vaultwarden Deployment
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-deployment
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vaultwarden
namespace: vaultwarden
labels:
app: vaultwarden
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: vaultwarden
template:
metadata:
labels:
app: vaultwarden
spec:
containers:
- name: vaultwarden
image: vaultwarden/server:testing
ports:
- containerPort: 8080
name: http
- containerPort: 3012
name: websocket
envFrom:
- configMapRef:
name: vaultwarden-config
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
volumeMounts:
- name: data
mountPath: /data
readinessProbe:
httpGet:
path: /alive
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /alive
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: data
persistentVolumeClaim:
claimName: vaultwarden-data
---
# Vaultwarden Service
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-service
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: v1
kind: Service
metadata:
name: vaultwarden-http
namespace: vaultwarden
labels:
app: vaultwarden
spec:
selector:
app: vaultwarden
ports:
- name: http
port: 8080
targetPort: 8080
- name: websocket
port: 3012
targetPort: 3012
type: ClusterIP
---
# SSL Certificate for Vaultwarden
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-certificate
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: vaultwarden-tls
namespace: emissary
spec:
secretName: vaultwarden-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- vault.nge6.com
---
# Ambassador Host for Vaultwarden
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-host
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
name: vaultwarden-host
namespace: emissary
spec:
hostname: vault.nge6.com
tlsSecret:
name: vaultwarden-tls
---
# Ambassador Mapping for Vaultwarden
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: vaultwarden-mapping
namespace: crossplane-system
spec:
providerConfigRef:
name: kubernetes-provider
forProvider:
manifest:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: vaultwarden-mapping
namespace: emissary
spec:
hostname: vault.nge6.com
prefix: /
service: http://pomerium-allinone.pomerium:443
timeout_ms: 30000
connect_timeout_ms: 10000