Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff4e87aca0 | ||
|
|
01561ea2cd | ||
|
|
7db427f332 | ||
|
|
41729bf256 | ||
|
|
fe914e2de4 | ||
|
|
0348329829 | ||
|
|
e257e3895b | ||
|
|
4fe2a2b5f3 | ||
|
|
690ff2a19c | ||
|
|
99e1818572 | ||
|
|
cf88753ff5 | ||
|
|
862a7619e2 | ||
|
|
5a29dcc236 | ||
|
|
bcfbfcd362 | ||
|
|
2676ecef5d | ||
|
|
b4de55f3c0 | ||
|
|
de6c9562f1 | ||
|
|
0c333375a5 | ||
|
|
5783547bd3 | ||
|
|
29c5b00c16 | ||
|
|
76b2090ccc | ||
|
|
bc83f7dd24 | ||
|
|
5768a4ff3b | ||
|
|
7ce98c4982 | ||
|
|
18f2654ff1 | ||
|
|
2fb63dded8 | ||
|
|
46bf1dc41b | ||
|
|
eda408aaea | ||
|
|
7b9da9236f | ||
|
|
f2a607fda1 | ||
|
|
d57bb01608 | ||
|
|
76f77fbc56 | ||
|
|
e90efbd016 |
No files matched your search
@@ -59,3 +59,6 @@ tags
|
||||
[._]*.un~
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Helm charts pulled by kustomize (--enable-helm) when rendering locally
|
||||
k8s-ng/**/charts/
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Brings the whole core state up on an empty cluster:
|
||||
# k8s-ng/bootstrap/bootstrap.sh <kube-context>
|
||||
# Installs Argo CD (the only thing that cannot install itself) and applies the
|
||||
# root application; Argo CD then pulls every core application in wave order.
|
||||
set -euo pipefail
|
||||
|
||||
ctx=${1:?usage: bootstrap.sh <kube-context>}
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
k="kubectl --context $ctx"
|
||||
|
||||
# Render in a scratch copy so kustomize does not leave downloaded charts in the repo.
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
cp -r "$here/../core/argocd" "$tmp/argocd"
|
||||
kubectl kustomize --enable-helm "$tmp/argocd" > "$tmp/argocd.yaml"
|
||||
|
||||
$k create namespace argocd --dry-run=client -o yaml | $k apply -f -
|
||||
# CRDs must exist before the Argo CD objects that use them: apply twice.
|
||||
$k apply --server-side --force-conflicts -f "$tmp/argocd.yaml" >/dev/null 2>&1 || true
|
||||
sleep 8
|
||||
$k apply --server-side --force-conflicts -f "$tmp/argocd.yaml"
|
||||
$k -n argocd rollout status deploy --timeout=300s
|
||||
$k -n argocd rollout status statefulset --timeout=300s
|
||||
|
||||
$k apply --server-side --force-conflicts -f "$here/root.yaml"
|
||||
echo "Core state requested. Watch it with: $k -n argocd get app"
|
||||
@@ -0,0 +1,29 @@
|
||||
# Root of the core state. Applying this one object makes Argo CD pull every core
|
||||
# application in wave order (see k8s-ng/core-state/values.yaml).
|
||||
# Note: there are deliberately no finalizers; deleting this Application keeps all
|
||||
# deployed services and data.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: core-state
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: core
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: argocd
|
||||
source:
|
||||
repoURL: https://gt.hexor.cy/ab/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s-ng/core-state
|
||||
helm:
|
||||
# Per-cluster overrides of core-state/values.yaml, for example:
|
||||
# valuesObject:
|
||||
# targetRevision: some-branch
|
||||
valuesObject: {}
|
||||
syncPolicy:
|
||||
automated:
|
||||
selfHeal: true
|
||||
prune: true
|
||||
syncOptions:
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v2
|
||||
name: core-state
|
||||
description: App of apps for the core services of the cluster
|
||||
version: 0.1.0
|
||||
@@ -0,0 +1,29 @@
|
||||
{{- range .Values.apps }}
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: {{ .name }}
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: {{ .wave | quote }}
|
||||
spec:
|
||||
project: {{ .project | default $.Values.project }}
|
||||
destination:
|
||||
server: {{ $.Values.destinationServer }}
|
||||
namespace: {{ .namespace }}
|
||||
source:
|
||||
repoURL: {{ $.Values.repoURL }}
|
||||
targetRevision: {{ $.Values.targetRevision }}
|
||||
path: {{ .path }}
|
||||
syncPolicy:
|
||||
automated:
|
||||
selfHeal: true
|
||||
prune: {{ if hasKey . "prune" }}{{ .prune }}{{ else }}true{{ end }}
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
{{- if .skipDryRunOnMissingResource }}
|
||||
- SkipDryRunOnMissingResource=true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,65 @@
|
||||
# Per-cluster variables. Override them in k8s-ng/bootstrap/root.yaml
|
||||
# (spec.source.helm.valuesObject) when the state is deployed to another cluster.
|
||||
repoURL: https://gt.hexor.cy/ab/homelab.git
|
||||
targetRevision: main
|
||||
destinationServer: https://kubernetes.default.svc
|
||||
project: core
|
||||
|
||||
# Core applications. Lower waves are synced first and must be Healthy before the
|
||||
# next wave starts. Every path is a kustomize (or plain manifest) directory.
|
||||
# wave 0: Argo CD itself and its projects
|
||||
# wave 1: operators (their CRDs are used by the later waves)
|
||||
# wave 2: the database cluster and its databases/users
|
||||
# wave 3: services that need the database and secrets
|
||||
apps:
|
||||
- name: argocd
|
||||
path: k8s-ng/core/argocd
|
||||
namespace: argocd
|
||||
wave: 0
|
||||
- name: cnpg
|
||||
path: k8s-ng/core/cnpg
|
||||
namespace: cnpg-system
|
||||
wave: 1
|
||||
- name: traefik-config
|
||||
path: k8s-ng/core/traefik-config
|
||||
namespace: kube-system
|
||||
wave: 1
|
||||
- name: reloader
|
||||
path: k8s-ng/core/reloader
|
||||
namespace: reloader
|
||||
wave: 1
|
||||
- name: cert-manager
|
||||
path: k8s-ng/core/cert-manager
|
||||
namespace: cert-manager
|
||||
wave: 1
|
||||
skipDryRunOnMissingResource: true # ClusterIssuers need the cert-manager CRDs
|
||||
- name: external-secrets
|
||||
path: k8s-ng/core/external-secrets
|
||||
namespace: external-secrets
|
||||
wave: 1
|
||||
- name: postgresql
|
||||
path: k8s-ng/core/postgresql
|
||||
namespace: psql
|
||||
wave: 2
|
||||
prune: false # never delete databases because of a manifest change
|
||||
skipDryRunOnMissingResource: true # CRDs come from the cnpg and external-secrets apps
|
||||
- name: vaultwarden
|
||||
path: k8s-ng/core/vaultwarden
|
||||
namespace: vaultwarden
|
||||
wave: 3
|
||||
skipDryRunOnMissingResource: true
|
||||
- name: keycloak
|
||||
path: k8s-ng/core/keycloak
|
||||
namespace: keycloak
|
||||
wave: 3
|
||||
skipDryRunOnMissingResource: true # ExternalSecret/Certificate CRDs come from earlier waves
|
||||
- name: furumi
|
||||
path: k8s-ng/core/furumi
|
||||
namespace: furumi
|
||||
wave: 5 # pinned to the music node; must not hold back the other waves
|
||||
skipDryRunOnMissingResource: true
|
||||
- name: secret-stores
|
||||
path: k8s-ng/core/secret-stores
|
||||
namespace: external-secrets
|
||||
wave: 4 # reads the Vaultwarden vault, so it comes after vaultwarden
|
||||
skipDryRunOnMissingResource: true # ClusterSecretStore CRD comes from the external-secrets app
|
||||
@@ -0,0 +1,66 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: AppProject
|
||||
metadata:
|
||||
name: core
|
||||
namespace: argocd
|
||||
spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
description: Hexor Home Lab Core
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: https://kubernetes.default.svc
|
||||
sourceRepos:
|
||||
- https://gt.hexor.cy/ab/homelab.git
|
||||
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: AppProject
|
||||
metadata:
|
||||
name: apps
|
||||
namespace: argocd
|
||||
spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
description: Hexor Home Lab Apps
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: https://kubernetes.default.svc
|
||||
sourceRepos:
|
||||
- https://gt.hexor.cy/ab/homelab.git
|
||||
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: AppProject
|
||||
metadata:
|
||||
name: games
|
||||
namespace: argocd
|
||||
spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
description: Hexor Home Lab Games
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: https://kubernetes.default.svc
|
||||
sourceRepos:
|
||||
- https://gt.hexor.cy/ab/homelab.git
|
||||
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: AppProject
|
||||
metadata:
|
||||
name: desktop
|
||||
namespace: argocd
|
||||
spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
description: Hexor Home Lab Desktop Apps
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: https://kubernetes.default.svc
|
||||
sourceRepos:
|
||||
- https://gt.hexor.cy/ab/homelab.git
|
||||
@@ -0,0 +1,26 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: argocd-dex-server
|
||||
namespace: argocd
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: dex-server
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 5556
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 5556
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 3
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: argocd
|
||||
namespace: argocd
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- ag.btwiusearch.net
|
||||
secretName: argocd-server-tls
|
||||
rules:
|
||||
- host: ag.btwiusearch.net
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: argocd-server
|
||||
port:
|
||||
number: 80
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ingress.yaml
|
||||
- appprojects.yaml
|
||||
|
||||
helmCharts:
|
||||
- name: argo-cd
|
||||
repo: https://argoproj.github.io/argo-helm
|
||||
version: 10.6.0
|
||||
releaseName: argocd
|
||||
namespace: argocd
|
||||
valuesFile: values.yaml
|
||||
includeCRDs: true
|
||||
|
||||
patches:
|
||||
- path: dex-probes-patch.yaml
|
||||
@@ -0,0 +1,115 @@
|
||||
# Argo CD Helm Chart Values (k8s-ng cluster)
|
||||
|
||||
global:
|
||||
domain: ag.btwiusearch.net
|
||||
logging:
|
||||
format: text
|
||||
level: info
|
||||
|
||||
crds:
|
||||
install: true
|
||||
keep: true # Recommended to keep true so CRDs are not deleted on uninstall
|
||||
|
||||
configs:
|
||||
params:
|
||||
server.insecure: "true" # TLS is terminated by the ingress
|
||||
cm:
|
||||
create: true
|
||||
exec.enabled: true
|
||||
kustomize.buildOptions: --enable-helm
|
||||
application.instanceLabelKey: argocd.argoproj.io/instance
|
||||
# Local admin account, kept as a break-glass login next to SSO; the initial
|
||||
# password is generated by Argo CD into the argocd-initial-admin-secret secret.
|
||||
# Set to false once SSO is trusted.
|
||||
admin.enabled: true
|
||||
statusbadge.enabled: true
|
||||
timeout.reconciliation: 60s
|
||||
# Lets sync waves in an app of apps wait for child Applications to be Healthy.
|
||||
resource.customizations.health.argoproj.io_Application: |
|
||||
hs = {}
|
||||
hs.status = "Progressing"
|
||||
hs.message = ""
|
||||
if obj.status ~= nil then
|
||||
if obj.status.health ~= nil then
|
||||
hs.status = obj.status.health.status
|
||||
if obj.status.health.message ~= nil then
|
||||
hs.message = obj.status.health.message
|
||||
end
|
||||
end
|
||||
end
|
||||
return hs
|
||||
# SSO through Dex -> Keycloak. The client id/secret come from the oidc-creds
|
||||
# Secret (labelled app.kubernetes.io/part-of=argocd), created by hand until
|
||||
# Vaultwarden + External Secrets are the source.
|
||||
dex.config: |
|
||||
connectors:
|
||||
- type: oidc
|
||||
id: keycloak
|
||||
name: Keycloak
|
||||
config:
|
||||
issuer: https://auth.hexor.cy/auth/realms/hexor
|
||||
clientID: $oidc-creds:id
|
||||
clientSecret: $oidc-creds:secret
|
||||
insecureEnableGroups: true
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- offline_access
|
||||
getUserInfo: true
|
||||
rbac:
|
||||
create: true
|
||||
policy.default: ""
|
||||
policy.csv: |
|
||||
g, game-servers-managers, GameServersManagersRole
|
||||
# Role permissions
|
||||
p, GameServersManagersRole, applications, get, games/*, allow
|
||||
p, GameServersManagersRole, applications, update, games/*, allow
|
||||
p, GameServersManagersRole, applications, sync, games/*, allow
|
||||
p, GameServersManagersRole, applications, override, games/*, allow
|
||||
p, GameServersManagersRole, applications, action/*, games/*, allow
|
||||
p, GameServersManagersRole, exec, create, games/*, allow
|
||||
p, GameServersManagersRole, logs, get, games/*, allow
|
||||
p, GameServersManagersRole, applications, delete, games/*, deny
|
||||
|
||||
# Admin policy
|
||||
g, argocd-admins, role:admin
|
||||
secret:
|
||||
createSecret: true
|
||||
|
||||
controller:
|
||||
replicas: 1
|
||||
|
||||
# Dex OIDC provider (Keycloak connector configured in configs.cm.dex.config)
|
||||
dex:
|
||||
replicas: 1
|
||||
enabled: true
|
||||
|
||||
redis:
|
||||
enabled: true
|
||||
|
||||
redis-ha:
|
||||
enabled: false
|
||||
|
||||
server:
|
||||
replicas: 1
|
||||
ingress:
|
||||
enabled: false
|
||||
certificate:
|
||||
enabled: false
|
||||
certificateSecret:
|
||||
enabled: false
|
||||
|
||||
repoServer:
|
||||
replicas: 1
|
||||
livenessProbe:
|
||||
timeoutSeconds: 10
|
||||
periodSeconds: 60
|
||||
|
||||
applicationSet:
|
||||
enabled: true
|
||||
replicas: 1
|
||||
|
||||
notifications:
|
||||
enabled: true
|
||||
replicas: 1
|
||||
@@ -0,0 +1,38 @@
|
||||
# Public certificates through DNS-01. The credential Secrets are created by hand
|
||||
# (they will move to Vaultwarden + External Secrets):
|
||||
# kubectl -n cert-manager create secret generic cloudflare-api-token --from-literal=apiToken=<token>
|
||||
# (Zone:DNS:Edit on the btwiusearch.net zone)
|
||||
# kubectl -n cert-manager create secret generic aws-api-key-secret \
|
||||
# --from-literal=accessKey=<id> --from-literal=secretKey=<secret>
|
||||
# (Route53 access to the hexor.cy zone)
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: k8s-master@hexor.cy
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-account-key
|
||||
solvers:
|
||||
- selector:
|
||||
dnsZones:
|
||||
- btwiusearch.net
|
||||
dns01:
|
||||
cloudflare:
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-api-token
|
||||
key: apiToken
|
||||
- selector:
|
||||
dnsZones:
|
||||
- hexor.cy
|
||||
dns01:
|
||||
route53:
|
||||
region: eu-north-1
|
||||
accessKeyIDSecretRef:
|
||||
name: aws-api-key-secret
|
||||
key: accessKey
|
||||
secretAccessKeySecretRef:
|
||||
name: aws-api-key-secret
|
||||
key: secretKey
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- issuer.yaml
|
||||
- self-signed.yaml
|
||||
|
||||
helmCharts:
|
||||
- name: cert-manager
|
||||
repo: https://charts.jetstack.io
|
||||
version: 1.21.1
|
||||
releaseName: cert-manager
|
||||
namespace: cert-manager
|
||||
valuesFile: values.yaml
|
||||
includeCRDs: true
|
||||
@@ -0,0 +1,29 @@
|
||||
# Private CA for internal services: issuer "hexor-lab".
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: selfsigned
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: hexor-ca-root
|
||||
namespace: cert-manager
|
||||
spec:
|
||||
isCA: true
|
||||
duration: 87600h # 10y
|
||||
commonName: hexor-ca
|
||||
secretName: hexor-ca-root-secret
|
||||
issuerRef:
|
||||
name: selfsigned
|
||||
kind: ClusterIssuer
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: hexor-lab
|
||||
spec:
|
||||
ca:
|
||||
secretName: hexor-ca-root-secret
|
||||
@@ -0,0 +1,2 @@
|
||||
crds:
|
||||
enabled: true
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
helmCharts:
|
||||
- name: cloudnative-pg
|
||||
repo: https://cloudnative-pg.github.io/charts
|
||||
version: 0.29.1
|
||||
releaseName: cnpg
|
||||
namespace: cnpg-system
|
||||
valuesFile: values.yaml
|
||||
includeCRDs: true
|
||||
@@ -0,0 +1,4 @@
|
||||
# CloudNativePG operator (chart 0.29.x = operator 1.30.x)
|
||||
|
||||
crds:
|
||||
create: true
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# The Vaultwarden bridge and the ClusterSecretStores are in ../secret-stores (they need Vaultwarden).
|
||||
|
||||
helmCharts:
|
||||
- name: external-secrets
|
||||
repo: https://charts.external-secrets.io
|
||||
version: 2.11.0
|
||||
releaseName: external-secrets
|
||||
namespace: external-secrets
|
||||
valuesFile: values.yaml
|
||||
includeCRDs: true
|
||||
@@ -0,0 +1 @@
|
||||
installCRDs: true
|
||||
@@ -0,0 +1,50 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: furumi-player
|
||||
labels:
|
||||
app: furumi-player
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate # the library is a hostPath on a single node
|
||||
selector:
|
||||
matchLabels:
|
||||
app: furumi-player
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: furumi-player
|
||||
annotations:
|
||||
reloader.stakater.com/auto: "true"
|
||||
spec:
|
||||
# The music library lives on this node's /data (LVM raid, not replicated).
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: music
|
||||
containers:
|
||||
- name: furumi-player
|
||||
image: ultradesu/furumusic:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: FURU_DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: furumi-creds
|
||||
key: PG_STRING
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
volumeMounts:
|
||||
- name: library
|
||||
mountPath: /media
|
||||
- name: inbox
|
||||
mountPath: /inbox
|
||||
volumes:
|
||||
- name: library
|
||||
hostPath:
|
||||
path: /data/furumi/library
|
||||
type: Directory
|
||||
- name: inbox
|
||||
hostPath:
|
||||
path: /data/furumi/inbox
|
||||
type: Directory
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: furumi
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: music.hexor.cy
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: furumi-player
|
||||
port:
|
||||
number: 8000
|
||||
tls:
|
||||
- secretName: furumi-tls
|
||||
hosts:
|
||||
- music.hexor.cy
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- secrets.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
@@ -0,0 +1,47 @@
|
||||
# Read-only access to the furumi-db Secret in the psql namespace
|
||||
# (see postgresql/databases/furumi.yaml for the other half).
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: db-credentials-reader
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: psql-credentials
|
||||
spec:
|
||||
provider:
|
||||
kubernetes:
|
||||
remoteNamespace: psql
|
||||
server:
|
||||
caProvider:
|
||||
type: ConfigMap
|
||||
name: kube-root-ca.crt
|
||||
key: ca.crt
|
||||
auth:
|
||||
serviceAccount:
|
||||
name: db-credentials-reader
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: furumi-creds
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: SecretStore
|
||||
name: psql-credentials
|
||||
target:
|
||||
name: furumi-creds
|
||||
template:
|
||||
data:
|
||||
PG_STRING: "postgresql://{{ .username }}:{{ .password }}@main-rw.psql.svc:5432/furumi"
|
||||
data:
|
||||
- secretKey: username
|
||||
remoteRef:
|
||||
key: furumi-db
|
||||
property: username
|
||||
- secretKey: password
|
||||
remoteRef:
|
||||
key: furumi-db
|
||||
property: password
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: furumi-player
|
||||
labels:
|
||||
app: furumi-player
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: furumi-player
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
@@ -0,0 +1,122 @@
|
||||
# rsauth2-proxy: forward-auth for services that have no OIDC of their own.
|
||||
# Needs, besides this file:
|
||||
# - Secret auth-proxy-creds (AUTH_PROXY_CLIENT_ID, AUTH_PROXY_CLIENT_SECRET,
|
||||
# AUTH_PROXY_COOKIE_SECRET); the client secret is the one of the Keycloak
|
||||
# client "rsauth2-proxy". Created by hand until Vaultwarden is the source.
|
||||
# - ConfigMap auth-proxy-routes, managed by Terraform (terraform/keycloak).
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: auth-proxy
|
||||
labels:
|
||||
app: auth-proxy
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: auth-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: auth-proxy
|
||||
spec:
|
||||
containers:
|
||||
- name: auth-proxy
|
||||
image: ultradesu/rsauth2-proxy:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: auth-proxy-creds
|
||||
env:
|
||||
- name: AUTH_PROXY_OIDC_ISSUER
|
||||
value: "https://auth.hexor.cy/auth/realms/hexor"
|
||||
- name: AUTH_PROXY_COOKIE_DOMAIN
|
||||
value: ".hexor.cy"
|
||||
- name: AUTH_PROXY_CALLBACK_URL
|
||||
value: "https://oauth.hexor.cy/callback"
|
||||
- name: AUTH_PROXY_ROUTES_FILE
|
||||
value: "/config/routes.yaml"
|
||||
- name: AUTH_PROXY_LOG_LEVEL
|
||||
value: "debug"
|
||||
volumeMounts:
|
||||
- name: routes
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 64Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 30
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumes:
|
||||
- name: routes
|
||||
configMap:
|
||||
name: auth-proxy-routes
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: auth-proxy
|
||||
labels:
|
||||
app: auth-proxy
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: auth-proxy
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: auth-proxy
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`oauth.hexor.cy`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: auth-proxy
|
||||
port: 80
|
||||
tls:
|
||||
secretName: auth-proxy-tls
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: auth-proxy-tls
|
||||
spec:
|
||||
secretName: auth-proxy-tls
|
||||
issuerRef:
|
||||
name: letsencrypt
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- oauth.hexor.cy
|
||||
@@ -0,0 +1,366 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: keycloak-hacker-theme
|
||||
namespace: keycloak
|
||||
data:
|
||||
theme.properties: |
|
||||
parent=keycloak.v2
|
||||
import=common/keycloak
|
||||
|
||||
styles=css/hacker.css
|
||||
|
||||
hacker.css: |
|
||||
/* ============================================================
|
||||
HEXOR — Hacker Terminal Theme for Keycloak
|
||||
============================================================ */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&display=swap');
|
||||
|
||||
:root {
|
||||
--hk-bg: #0a0a0a;
|
||||
--hk-panel: #0d0d0d;
|
||||
--hk-border: #00ff4180;
|
||||
--hk-green: #00ff41;
|
||||
--hk-green-dim: #00cc33;
|
||||
--hk-green-glow: rgba(0, 255, 65, 0.15);
|
||||
--hk-green-glow-strong: rgba(0, 255, 65, 0.35);
|
||||
--hk-cyan: #00e5ff;
|
||||
--hk-red: #ff3333;
|
||||
--hk-text: #b0ffb0;
|
||||
--hk-text-dim: #5a8a5a;
|
||||
--hk-input-bg: #050505;
|
||||
--hk-font: 'Fira Code', 'Cascadia Code', 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
/* -- Force dark always --------------------------------------------------- */
|
||||
html.login-pf,
|
||||
html.login-pf.pf-v5-theme-dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/* -- Background: scanlines + CRT ---------------------------------------- */
|
||||
body#keycloak-bg {
|
||||
font-family: var(--hk-font) !important;
|
||||
background: var(--hk-bg) !important;
|
||||
color: var(--hk-text) !important;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body#keycloak-bg::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(0, 255, 65, 0.03) 2px,
|
||||
rgba(0, 255, 65, 0.03) 4px
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* -- Login container ----------------------------------------------------- */
|
||||
.pf-v5-c-login {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-login__container {
|
||||
max-width: 480px !important;
|
||||
}
|
||||
|
||||
/* -- Header / Brand ------------------------------------------------------ */
|
||||
#kc-header-wrapper {
|
||||
font-family: var(--hk-font) !important;
|
||||
color: var(--hk-green) !important;
|
||||
text-shadow: 0 0 10px var(--hk-green), 0 0 30px var(--hk-green-glow);
|
||||
font-size: 1.5rem !important;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#kc-header-wrapper .kc-logo-text span::before {
|
||||
content: '> ';
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* -- Main card ----------------------------------------------------------- */
|
||||
.pf-v5-c-login__main {
|
||||
background: var(--hk-panel) !important;
|
||||
border: 1px solid var(--hk-border) !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow:
|
||||
0 0 15px var(--hk-green-glow),
|
||||
inset 0 0 30px rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
/* -- Title --------------------------------------------------------------- */
|
||||
h1#kc-page-title,
|
||||
.pf-v5-c-title {
|
||||
font-family: var(--hk-font) !important;
|
||||
color: var(--hk-green) !important;
|
||||
text-shadow: 0 0 8px var(--hk-green-glow);
|
||||
font-weight: 600 !important;
|
||||
font-size: 1.25rem !important;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* -- Labels -------------------------------------------------------------- */
|
||||
.pf-v5-c-form__label-text {
|
||||
font-family: var(--hk-font) !important;
|
||||
color: var(--hk-green-dim) !important;
|
||||
font-size: 0.85rem !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
/* -- Input fields -------------------------------------------------------- */
|
||||
.pf-v5-c-form-control {
|
||||
background-color: var(--hk-input-bg) !important;
|
||||
border: 1px solid var(--hk-border) !important;
|
||||
border-radius: 0 !important;
|
||||
color: var(--hk-green) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
font-size: 0.9rem !important;
|
||||
caret-color: var(--hk-green);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.pf-v5-c-form-control:focus-within,
|
||||
.pf-v5-c-form-control:focus {
|
||||
border-color: var(--hk-green) !important;
|
||||
box-shadow: 0 0 8px var(--hk-green-glow), inset 0 0 4px var(--hk-green-glow) !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-form-control input {
|
||||
color: var(--hk-green) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-form-control input::placeholder {
|
||||
color: var(--hk-text-dim) !important;
|
||||
}
|
||||
|
||||
/* -- Primary button (Sign In) -------------------------------------------- */
|
||||
.pf-v5-c-button.pf-m-primary {
|
||||
background: transparent !important;
|
||||
border: 1px solid var(--hk-green) !important;
|
||||
border-radius: 0 !important;
|
||||
color: var(--hk-green) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 0.9rem !important;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pf-v5-c-button.pf-m-primary:hover {
|
||||
background: var(--hk-green-glow) !important;
|
||||
box-shadow: 0 0 15px var(--hk-green-glow-strong), inset 0 0 15px var(--hk-green-glow) !important;
|
||||
color: #fff !important;
|
||||
text-shadow: 0 0 5px var(--hk-green);
|
||||
}
|
||||
|
||||
/* -- Secondary button (Try Another Way, Passkey) ------------------------- */
|
||||
.pf-v5-c-button.pf-m-secondary {
|
||||
background: transparent !important;
|
||||
border: 1px solid var(--hk-cyan) !important;
|
||||
border-radius: 0 !important;
|
||||
color: var(--hk-cyan) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 0.85rem !important;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.pf-v5-c-button.pf-m-secondary:hover {
|
||||
background: rgba(0, 229, 255, 0.1) !important;
|
||||
box-shadow: 0 0 12px rgba(0, 229, 255, 0.3) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* -- Passkey authenticate button ----------------------------------------- */
|
||||
#authenticateWebAuthnButton {
|
||||
background: transparent !important;
|
||||
border: 1px solid var(--hk-cyan) !important;
|
||||
border-radius: 0 !important;
|
||||
color: var(--hk-cyan) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 0.85rem !important;
|
||||
padding: 0.75rem 1rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#authenticateWebAuthnButton::before {
|
||||
content: '\f084 ';
|
||||
font-family: 'Font Awesome 6 Free', 'FontAwesome';
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
#authenticateWebAuthnButton:hover {
|
||||
background: rgba(0, 229, 255, 0.1) !important;
|
||||
box-shadow: 0 0 15px rgba(0, 229, 255, 0.3), inset 0 0 10px rgba(0, 229, 255, 0.1) !important;
|
||||
color: #fff !important;
|
||||
text-shadow: 0 0 5px var(--hk-cyan);
|
||||
}
|
||||
|
||||
/* -- Show password button ------------------------------------------------ */
|
||||
.pf-v5-c-button.pf-m-control {
|
||||
background: var(--hk-input-bg) !important;
|
||||
border: 1px solid var(--hk-border) !important;
|
||||
border-left: none !important;
|
||||
border-radius: 0 !important;
|
||||
color: var(--hk-text-dim) !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-button.pf-m-control:hover {
|
||||
color: var(--hk-green) !important;
|
||||
}
|
||||
|
||||
/* -- Social providers (Google, etc) -------------------------------------- */
|
||||
.pf-v5-c-login__main-footer {
|
||||
border-top: 1px solid var(--hk-border) !important;
|
||||
}
|
||||
|
||||
.kc-social-section .pf-v5-c-button.pf-m-secondary {
|
||||
border-color: var(--hk-text-dim) !important;
|
||||
color: var(--hk-text) !important;
|
||||
}
|
||||
|
||||
.kc-social-section .pf-v5-c-button.pf-m-secondary:hover {
|
||||
border-color: var(--hk-green) !important;
|
||||
color: var(--hk-green) !important;
|
||||
box-shadow: 0 0 10px var(--hk-green-glow) !important;
|
||||
}
|
||||
|
||||
/* -- Try another way link ------------------------------------------------ */
|
||||
#try-another-way {
|
||||
color: var(--hk-cyan) !important;
|
||||
text-decoration: none !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
}
|
||||
|
||||
#try-another-way:hover {
|
||||
text-shadow: 0 0 8px rgba(0, 229, 255, 0.5);
|
||||
}
|
||||
|
||||
/* -- Links --------------------------------------------------------------- */
|
||||
a {
|
||||
color: var(--hk-cyan) !important;
|
||||
text-decoration: none;
|
||||
transition: text-shadow 0.2s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-shadow: 0 0 6px rgba(0, 229, 255, 0.4);
|
||||
}
|
||||
|
||||
/* -- Alerts / messages --------------------------------------------------- */
|
||||
.pf-v5-c-alert {
|
||||
background: rgba(0, 255, 65, 0.05) !important;
|
||||
border: 1px solid var(--hk-border) !important;
|
||||
border-radius: 0 !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-alert.pf-m-danger {
|
||||
border-color: var(--hk-red) !important;
|
||||
background: rgba(255, 51, 51, 0.05) !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-alert__title {
|
||||
color: var(--hk-text) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
}
|
||||
|
||||
/* -- Checkbox (Remember me) ---------------------------------------------- */
|
||||
.pf-v5-c-check__label {
|
||||
color: var(--hk-text-dim) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
/* -- Helper text (Forgot password, etc.) --------------------------------- */
|
||||
.pf-v5-c-helper-text .pf-v5-c-button.pf-m-link {
|
||||
color: var(--hk-cyan) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
/* -- Select auth list (Try another way options) -------------------------- */
|
||||
.select-auth-container {
|
||||
background: var(--hk-panel) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-data-list__item {
|
||||
border-color: var(--hk-border) !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.pf-v5-c-data-list__item:hover {
|
||||
background: var(--hk-green-glow) !important;
|
||||
}
|
||||
|
||||
.select-auth-box-headline {
|
||||
color: var(--hk-green) !important;
|
||||
}
|
||||
|
||||
.select-auth-box-desc {
|
||||
color: var(--hk-text-dim) !important;
|
||||
}
|
||||
|
||||
/* -- Footer -------------------------------------------------------------- */
|
||||
.pf-v5-c-login__main-footer-band {
|
||||
background: transparent !important;
|
||||
border-top: 1px solid var(--hk-border) !important;
|
||||
}
|
||||
|
||||
/* -- Language selector --------------------------------------------------- */
|
||||
select#login-select-toggle {
|
||||
background: var(--hk-input-bg) !important;
|
||||
color: var(--hk-green) !important;
|
||||
border-color: var(--hk-border) !important;
|
||||
font-family: var(--hk-font) !important;
|
||||
}
|
||||
|
||||
/* -- Blinking cursor animation for header -------------------------------- */
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
#kc-header-wrapper::after {
|
||||
content: '_';
|
||||
animation: blink 1s step-end infinite;
|
||||
color: var(--hk-green);
|
||||
}
|
||||
|
||||
/* -- Subtle CRT flicker ------------------------------------------------- */
|
||||
@keyframes flicker {
|
||||
0% { opacity: 0.97; }
|
||||
5% { opacity: 0.95; }
|
||||
10% { opacity: 0.98; }
|
||||
15% { opacity: 0.96; }
|
||||
20% { opacity: 0.99; }
|
||||
100% { opacity: 0.98; }
|
||||
}
|
||||
|
||||
.pf-v5-c-login__main {
|
||||
animation: flicker 4s infinite;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- secrets.yaml
|
||||
- hacker-theme-configmap.yaml
|
||||
- auth-proxy.yaml
|
||||
# ConfigMap auth-proxy-routes is managed by Terraform (terraform/keycloak, kubernetes_config_map_v1).
|
||||
|
||||
helmCharts:
|
||||
- name: keycloakx
|
||||
repo: https://codecentric.github.io/helm-charts
|
||||
version: 7.3.1
|
||||
releaseName: keycloak
|
||||
namespace: keycloak
|
||||
valuesFile: values.yaml
|
||||
@@ -0,0 +1,80 @@
|
||||
# Read-only access to the keycloak-db Secret in the psql namespace
|
||||
# (see postgresql/databases/keycloak.yaml for the other half).
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: db-credentials-reader
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: psql-credentials
|
||||
spec:
|
||||
provider:
|
||||
kubernetes:
|
||||
remoteNamespace: psql
|
||||
server:
|
||||
caProvider:
|
||||
type: ConfigMap
|
||||
name: kube-root-ca.crt
|
||||
key: ca.crt
|
||||
auth:
|
||||
serviceAccount:
|
||||
name: db-credentials-reader
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: keycloak-creds
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: SecretStore
|
||||
name: psql-credentials
|
||||
target:
|
||||
name: keycloak-creds
|
||||
template:
|
||||
data:
|
||||
KC_DB_USERNAME: "{{ .username }}"
|
||||
KC_DB_PASSWORD: "{{ .password }}"
|
||||
data:
|
||||
- secretKey: username
|
||||
remoteRef:
|
||||
key: keycloak-db
|
||||
property: username
|
||||
- secretKey: password
|
||||
remoteRef:
|
||||
key: keycloak-db
|
||||
property: password
|
||||
---
|
||||
# Bootstrap admin: only used when the database has no admin user yet (a fresh
|
||||
# install). With the restored database the existing admin accounts are kept.
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: keycloak-bootstrap
|
||||
spec:
|
||||
length: 40
|
||||
digits: 10
|
||||
symbols: 0
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: keycloak-bootstrap
|
||||
spec:
|
||||
refreshPolicy: CreatedOnce
|
||||
target:
|
||||
name: keycloak-bootstrap
|
||||
template:
|
||||
data:
|
||||
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||
KC_BOOTSTRAP_ADMIN_PASSWORD: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: keycloak-bootstrap
|
||||
@@ -0,0 +1,75 @@
|
||||
replicas: 1
|
||||
|
||||
image:
|
||||
repository: quay.io/keycloak/keycloak
|
||||
tag: "26.5.6"
|
||||
|
||||
command:
|
||||
- "/opt/keycloak/bin/kc.sh"
|
||||
- "start"
|
||||
- "--http-port=8080"
|
||||
- "--hostname-strict=false"
|
||||
- "--proxy-headers=xforwarded"
|
||||
|
||||
extraEnvFrom: |
|
||||
- secretRef:
|
||||
name: keycloak-creds
|
||||
- secretRef:
|
||||
name: keycloak-bootstrap
|
||||
|
||||
extraEnv: |
|
||||
- name: KC_HOSTNAME
|
||||
value: auth.hexor.cy
|
||||
- name: JAVA_OPTS_APPEND
|
||||
value: "-Djgroups.dns.query=keycloak-keycloakx-headless.keycloak.svc"
|
||||
|
||||
extraVolumes: |
|
||||
- name: hacker-theme
|
||||
configMap:
|
||||
name: keycloak-hacker-theme
|
||||
|
||||
extraVolumeMounts: |
|
||||
- name: hacker-theme
|
||||
mountPath: /opt/keycloak/themes/hacker/login/theme.properties
|
||||
subPath: theme.properties
|
||||
- name: hacker-theme
|
||||
mountPath: /opt/keycloak/themes/hacker/login/css/hacker.css
|
||||
subPath: hacker.css
|
||||
|
||||
dbchecker:
|
||||
enabled: true
|
||||
|
||||
database:
|
||||
vendor: postgres
|
||||
hostname: main-rw.psql.svc
|
||||
port: 5432
|
||||
database: keycloak
|
||||
existingSecret: keycloak-creds
|
||||
existingSecretKey: KC_DB_PASSWORD
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
# HTTP is redirected to HTTPS by the cluster-wide Traefik default (core/traefik-config).
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: traefik
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
rules:
|
||||
- host: auth.hexor.cy
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: keycloak-tls
|
||||
hosts:
|
||||
- auth.hexor.cy
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "3"
|
||||
memory: 2Gi
|
||||
@@ -0,0 +1,48 @@
|
||||
# Main HA PostgreSQL cluster: one primary and one streaming replica, each on its
|
||||
# own node with a node-local volume. Replication is asynchronous (nodes sit in
|
||||
# different regions), so a failover can lose the last few transactions.
|
||||
#
|
||||
# Nodes eligible for instances carry the label workload/postgres=true. To move a
|
||||
# replica: label the new node, unlabel the old one, then recreate the instance
|
||||
# (kubectl cnpg destroy main <N>); the operator re-clones it from the primary.
|
||||
#
|
||||
# Endpoints inside the cluster: main-rw (primary), main-ro (replicas),
|
||||
# main-r (any instance) in the psql namespace.
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: main
|
||||
namespace: psql
|
||||
spec:
|
||||
description: Main HA PostgreSQL cluster
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:17
|
||||
instances: 2
|
||||
enableSuperuserAccess: false
|
||||
|
||||
# Roll updates replica-first and hand over by switchover instead of restarting the primary.
|
||||
primaryUpdateStrategy: unsupervised
|
||||
primaryUpdateMethod: switchover
|
||||
|
||||
storage:
|
||||
storageClass: local-path
|
||||
size: 20Gi # local-path volumes cannot be expanded later
|
||||
|
||||
affinity:
|
||||
enablePodAntiAffinity: true
|
||||
podAntiAffinityType: required
|
||||
topologyKey: kubernetes.io/hostname
|
||||
nodeSelector:
|
||||
workload/postgres: "true"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "200"
|
||||
shared_buffers: 512MB
|
||||
effective_cache_size: 1536MB
|
||||
@@ -0,0 +1,83 @@
|
||||
# Database "furumi": password, role, database and read access for the app.
|
||||
# To add another database copy this file and replace the name.
|
||||
|
||||
# Generated once; the role and the application both read this Secret.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: furumi-db
|
||||
namespace: psql
|
||||
spec:
|
||||
refreshPolicy: CreatedOnce
|
||||
target:
|
||||
name: furumi-db
|
||||
template:
|
||||
type: kubernetes.io/basic-auth
|
||||
metadata:
|
||||
labels:
|
||||
cnpg.io/reload: "true"
|
||||
data:
|
||||
username: furumi
|
||||
password: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: db-password
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: DatabaseRole
|
||||
metadata:
|
||||
name: main-furumi
|
||||
namespace: psql
|
||||
spec:
|
||||
cluster:
|
||||
name: main
|
||||
name: furumi
|
||||
login: true
|
||||
passwordSecret:
|
||||
name: furumi-db
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Database
|
||||
metadata:
|
||||
name: main-furumi
|
||||
namespace: psql
|
||||
spec:
|
||||
cluster:
|
||||
name: main
|
||||
name: furumi
|
||||
owner: furumi
|
||||
extensions:
|
||||
- name: pg_trgm
|
||||
databaseReclaimPolicy: retain
|
||||
---
|
||||
# Lets the furumi namespace read only its own credentials Secret.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: furumi-db-reader
|
||||
namespace: psql
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: [secrets]
|
||||
resourceNames: [furumi-db]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups: [authorization.k8s.io]
|
||||
resources: [selfsubjectrulesreviews]
|
||||
verbs: [create]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: furumi-db-reader
|
||||
namespace: psql
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: furumi-db-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: db-credentials-reader
|
||||
namespace: furumi
|
||||
@@ -0,0 +1,81 @@
|
||||
# Database "keycloak": password, role, database and read access for the app.
|
||||
# To add another database copy this file and replace the name.
|
||||
|
||||
# Generated once; the role and the application both read this Secret.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: keycloak-db
|
||||
namespace: psql
|
||||
spec:
|
||||
refreshPolicy: CreatedOnce
|
||||
target:
|
||||
name: keycloak-db
|
||||
template:
|
||||
type: kubernetes.io/basic-auth
|
||||
metadata:
|
||||
labels:
|
||||
cnpg.io/reload: "true"
|
||||
data:
|
||||
username: keycloak
|
||||
password: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: db-password
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: DatabaseRole
|
||||
metadata:
|
||||
name: main-keycloak
|
||||
namespace: psql
|
||||
spec:
|
||||
cluster:
|
||||
name: main
|
||||
name: keycloak
|
||||
login: true
|
||||
passwordSecret:
|
||||
name: keycloak-db
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Database
|
||||
metadata:
|
||||
name: main-keycloak
|
||||
namespace: psql
|
||||
spec:
|
||||
cluster:
|
||||
name: main
|
||||
name: keycloak
|
||||
owner: keycloak
|
||||
databaseReclaimPolicy: retain
|
||||
---
|
||||
# Lets the keycloak namespace read only its own credentials Secret.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: keycloak-db-reader
|
||||
namespace: psql
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: [secrets]
|
||||
resourceNames: [keycloak-db]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups: [authorization.k8s.io]
|
||||
resources: [selfsubjectrulesreviews]
|
||||
verbs: [create]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: keycloak-db-reader
|
||||
namespace: psql
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: keycloak-db-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: db-credentials-reader
|
||||
namespace: keycloak
|
||||
@@ -0,0 +1,81 @@
|
||||
# Database "vaultwarden": password, role, database and read access for the app.
|
||||
# To add another database copy this file and replace the name.
|
||||
|
||||
# Generated once; the role and the application both read this Secret.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: vaultwarden-db
|
||||
namespace: psql
|
||||
spec:
|
||||
refreshPolicy: CreatedOnce
|
||||
target:
|
||||
name: vaultwarden-db
|
||||
template:
|
||||
type: kubernetes.io/basic-auth
|
||||
metadata:
|
||||
labels:
|
||||
cnpg.io/reload: "true"
|
||||
data:
|
||||
username: vaultwarden
|
||||
password: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: db-password
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: DatabaseRole
|
||||
metadata:
|
||||
name: main-vaultwarden
|
||||
namespace: psql
|
||||
spec:
|
||||
cluster:
|
||||
name: main
|
||||
name: vaultwarden
|
||||
login: true
|
||||
passwordSecret:
|
||||
name: vaultwarden-db
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Database
|
||||
metadata:
|
||||
name: main-vaultwarden
|
||||
namespace: psql
|
||||
spec:
|
||||
cluster:
|
||||
name: main
|
||||
name: vaultwarden
|
||||
owner: vaultwarden
|
||||
databaseReclaimPolicy: retain
|
||||
---
|
||||
# Lets the vaultwarden namespace read only its own credentials Secret.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: vaultwarden-db-reader
|
||||
namespace: psql
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: [secrets]
|
||||
resourceNames: [vaultwarden-db]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups: [authorization.k8s.io]
|
||||
resources: [selfsubjectrulesreviews]
|
||||
verbs: [create]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: vaultwarden-db-reader
|
||||
namespace: psql
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: vaultwarden-db-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: db-credentials-reader
|
||||
namespace: vaultwarden
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- cluster.yaml
|
||||
- password-generator.yaml
|
||||
- databases/vaultwarden.yaml
|
||||
- databases/keycloak.yaml
|
||||
- databases/furumi.yaml
|
||||
@@ -0,0 +1,12 @@
|
||||
# Random passwords for database roles. Referenced by the ExternalSecrets in databases/.
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: db-password
|
||||
namespace: psql
|
||||
spec:
|
||||
length: 40
|
||||
digits: 10
|
||||
symbols: 0 # the password ends up inside a connection URL
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
helmCharts:
|
||||
- name: reloader
|
||||
repo: https://stakater.github.io/stakater-charts
|
||||
version: 2.2.18
|
||||
releaseName: reloader
|
||||
namespace: reloader
|
||||
valuesFile: values.yaml
|
||||
@@ -0,0 +1,24 @@
|
||||
reloader:
|
||||
watchGlobally: true
|
||||
autoReloadAll: true
|
||||
reloadOnCreate: true
|
||||
reloadOnDelete: false
|
||||
reloadStrategy: annotations
|
||||
ignoreConfigMaps: false
|
||||
ignoreSecrets: false
|
||||
ignoreJobs: false
|
||||
ignoreCronJobs: false
|
||||
enableHA: true
|
||||
syncAfterRestart: true
|
||||
logLevel: info
|
||||
rbac:
|
||||
enabled: true
|
||||
deployment:
|
||||
replicas: 2
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 150m
|
||||
memory: 512Mi
|
||||
@@ -0,0 +1,26 @@
|
||||
FROM debian:sid
|
||||
|
||||
ENV BW_CLI_VERSION=2025.12.1
|
||||
|
||||
RUN apt update && \
|
||||
apt install -y wget unzip && \
|
||||
wget https://github.com/bitwarden/clients/releases/download/cli-v${BW_CLI_VERSION}/bw-linux-${BW_CLI_VERSION}.zip && \
|
||||
unzip bw-linux-${BW_CLI_VERSION}.zip && \
|
||||
chmod +x bw && \
|
||||
mv bw /usr/local/bin/bw && \
|
||||
rm -rfv *.zip && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Build entrypoint script
|
||||
RUN echo '#!/bin/sh\n\
|
||||
set -e\n\
|
||||
bw config server ${BW_HOST}\n\
|
||||
export BW_SESSION=$(bw login ${BW_USER} --passwordenv BW_PASSWORD --raw)\n\
|
||||
bw unlock --check\n\
|
||||
echo "Running \`bw server\` on port 8087"\n\
|
||||
bw serve --hostname 0.0.0.0\n' > /entrypoint.sh && \
|
||||
chmod +x /entrypoint.sh
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# Bridge between External Secrets and Vaultwarden: `bw serve` logged in as the
|
||||
# bot account. Needs the Secret bitwarden-cli (created by hand; the bot logs in
|
||||
# with email + password, so keep password login enabled for it, i.e. no
|
||||
# SSO_ONLY, or switch the image to API-key login):
|
||||
# kubectl -n external-secrets create secret generic bitwarden-cli \
|
||||
# --from-literal=BW_HOST=https://vw.btwiusearch.net \
|
||||
# --from-literal=BW_USERNAME=<bot email> --from-literal=BW_PASSWORD=<password>
|
||||
# Image source: Dockerfile-bitwarden-cli in this directory.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bitwarden-cli
|
||||
labels:
|
||||
reloader.stakater.com/auto: "true"
|
||||
app.kubernetes.io/instance: bitwarden-cli
|
||||
app.kubernetes.io/name: bitwarden-cli
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: bitwarden-cli
|
||||
app.kubernetes.io/instance: bitwarden-cli
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: bitwarden-cli
|
||||
app.kubernetes.io/instance: bitwarden-cli
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
containers:
|
||||
- name: bitwarden-cli
|
||||
image: ultradesu/bitwarden-client:2025.12.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "300m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "1000m"
|
||||
env:
|
||||
- name: BW_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bitwarden-cli
|
||||
key: BW_HOST
|
||||
- name: BW_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bitwarden-cli
|
||||
key: BW_USERNAME
|
||||
- name: BW_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bitwarden-cli
|
||||
key: BW_PASSWORD
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8087
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- wget
|
||||
- -q
|
||||
- http://127.0.0.1:8087/sync
|
||||
- --post-data=''
|
||||
initialDelaySeconds: 20
|
||||
failureThreshold: 3
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 120
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 8087
|
||||
initialDelaySeconds: 20
|
||||
failureThreshold: 3
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 10
|
||||
startupProbe:
|
||||
tcpSocket:
|
||||
port: 8087
|
||||
initialDelaySeconds: 10
|
||||
failureThreshold: 30
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 5
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: bitwarden-cli
|
||||
labels:
|
||||
app.kubernetes.io/instance: bitwarden-cli
|
||||
app.kubernetes.io/name: bitwarden-cli
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8087
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: bitwarden-cli
|
||||
app.kubernetes.io/instance: bitwarden-cli
|
||||
---
|
||||
# Only the External Secrets controller may talk to the bridge: it serves the whole vault.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: external-secret-2-bw-cli
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: bitwarden-cli
|
||||
app.kubernetes.io/name: bitwarden-cli
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: external-secrets
|
||||
app.kubernetes.io/name: external-secrets
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- bitwarden-cli.yaml
|
||||
- stores.yaml
|
||||
@@ -0,0 +1,47 @@
|
||||
# ClusterSecretStores in front of the Vaultwarden vault that belongs to the bot
|
||||
# account (the vault holds server secrets only, nothing personal).
|
||||
#
|
||||
# `key` may be either
|
||||
# - the item UUID (old style, changes whenever items are imported again), or
|
||||
# - the item path: the item NAME, which by convention is the full path
|
||||
# k8s/<namespace>/<what> e.g. k8s/cert-manager/cloudflare-api-token
|
||||
# Names are looked up by exact match (a name that is a substring of another
|
||||
# item name, login or URL is fine). Do not use double quotes in names.
|
||||
#
|
||||
# vaultwarden-login: `property` is a path inside the item: login.username,
|
||||
# login.password, notes, fields[0].value, ...
|
||||
# vaultwarden-fields: `property` is the NAME of a custom field of the item.
|
||||
#
|
||||
# data:
|
||||
# - secretKey: password
|
||||
# sourceRef:
|
||||
# storeRef: {name: vaultwarden-login, kind: ClusterSecretStore}
|
||||
# remoteRef:
|
||||
# key: k8s/cert-manager/cloudflare-api-token
|
||||
# property: login.password
|
||||
#
|
||||
# The bridge syncs the vault every couple of minutes, so a new item becomes
|
||||
# visible to ESO after that.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: vaultwarden-login
|
||||
spec:
|
||||
provider:
|
||||
webhook:
|
||||
url: 'http://bitwarden-cli.external-secrets.svc:8087/{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}object/item/{{ .remoteRef.key }}{{ else }}list/object/items?search={{ .remoteRef.key }}{{ end }}'
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
result:
|
||||
jsonPath: '{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}$.data.{{ .remoteRef.property }}{{ else }}$.data.data[?@.name=="{{ .remoteRef.key }}"].{{ .remoteRef.property }}{{ end }}'
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: vaultwarden-fields
|
||||
spec:
|
||||
provider:
|
||||
webhook:
|
||||
url: 'http://bitwarden-cli.external-secrets.svc:8087/{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}object/item/{{ .remoteRef.key }}{{ else }}list/object/items?search={{ .remoteRef.key }}{{ end }}'
|
||||
result:
|
||||
jsonPath: '{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}$.data.fields[?@.name=="{{ .remoteRef.property }}"].value{{ else }}$.data.data[?@.name=="{{ .remoteRef.key }}"].fields[?@.name=="{{ .remoteRef.property }}"].value{{ end }}'
|
||||
@@ -0,0 +1,16 @@
|
||||
# Configuration of the Traefik that ships with k3s.
|
||||
# Ingresses without a router.entrypoints annotation now use only "websecure", so
|
||||
# plain HTTP is redirected to HTTPS by default (see http-redirect.yaml).
|
||||
# To publish an Ingress over plain HTTP add the annotation
|
||||
# traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
# (or "web,websecure" to serve both without a redirect).
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChartConfig
|
||||
metadata:
|
||||
name: traefik
|
||||
namespace: kube-system
|
||||
spec:
|
||||
valuesContent: |-
|
||||
ports:
|
||||
websecure:
|
||||
asDefault: true
|
||||
@@ -0,0 +1,29 @@
|
||||
# Catch-all on the "web" entrypoint with the lowest priority: every host that has
|
||||
# no explicit HTTP router of its own is redirected to HTTPS.
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: redirect-https
|
||||
namespace: kube-system
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: http-redirect
|
||||
namespace: kube-system
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: HostRegexp(`.+`)
|
||||
kind: Rule
|
||||
priority: 1
|
||||
middlewares:
|
||||
- name: redirect-https
|
||||
services:
|
||||
- name: noop@internal
|
||||
kind: TraefikService
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- helmchartconfig.yaml
|
||||
- http-redirect.yaml
|
||||
@@ -0,0 +1,77 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vaultwarden
|
||||
labels:
|
||||
app: vaultwarden
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate # the RWO local volume cannot be mounted by two pods
|
||||
selector:
|
||||
matchLabels:
|
||||
app: vaultwarden
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: vaultwarden
|
||||
spec:
|
||||
containers:
|
||||
- name: vaultwarden
|
||||
image: vaultwarden/server:1.37.3
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
cpu: 100m
|
||||
limits:
|
||||
memory: 512Mi
|
||||
# SSO is switched on by the optional Secret vaultwarden-sso, which holds
|
||||
# SSO_ENABLED=true and SSO_CLIENT_SECRET (the secret of the Keycloak client
|
||||
# "Vaultwarden"). Without it Vaultwarden starts with SSO off.
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: vaultwarden-sso
|
||||
optional: true
|
||||
env:
|
||||
- name: DOMAIN
|
||||
value: https://vw.btwiusearch.net
|
||||
- name: ORG_GROUPS_ENABLED
|
||||
value: "true"
|
||||
- name: SSO_AUTHORITY
|
||||
value: https://auth.hexor.cy/auth/realms/hexor
|
||||
- name: SSO_CLIENT_ID
|
||||
value: Vaultwarden
|
||||
- name: SSO_SCOPES
|
||||
value: openid profile email offline_access
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: vaultwarden-db-url
|
||||
key: DATABASE_URL
|
||||
- name: ADMIN_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: admin-token
|
||||
key: ADMIN_TOKEN
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /alive
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /alive
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: vaultwarden-data
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: vaultwarden
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
spec:
|
||||
rules:
|
||||
- host: vw.btwiusearch.net
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: vaultwarden
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- secretName: vaultwarden-tls
|
||||
hosts:
|
||||
- vw.btwiusearch.net
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- secrets.yaml
|
||||
- pvc.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
@@ -0,0 +1,12 @@
|
||||
# Attachments, sends, icon cache and RSA keys live here; the database is in PostgreSQL.
|
||||
# A local-path volume is bound to one node: the pod stays on the node it first ran on.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: vaultwarden-data
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
@@ -0,0 +1,78 @@
|
||||
# Read-only access to the vaultwarden-db Secret in the psql namespace
|
||||
# (see postgresql/databases/vaultwarden.yaml for the other half).
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: db-credentials-reader
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: psql-credentials
|
||||
spec:
|
||||
provider:
|
||||
kubernetes:
|
||||
remoteNamespace: psql
|
||||
server:
|
||||
caProvider:
|
||||
type: ConfigMap
|
||||
name: kube-root-ca.crt
|
||||
key: ca.crt
|
||||
auth:
|
||||
serviceAccount:
|
||||
name: db-credentials-reader
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: vaultwarden-db-url
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: SecretStore
|
||||
name: psql-credentials
|
||||
target:
|
||||
name: vaultwarden-db-url
|
||||
template:
|
||||
data:
|
||||
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@main-rw.psql.svc:5432/vaultwarden"
|
||||
data:
|
||||
- secretKey: username
|
||||
remoteRef:
|
||||
key: vaultwarden-db
|
||||
property: username
|
||||
- secretKey: password
|
||||
remoteRef:
|
||||
key: vaultwarden-db
|
||||
property: password
|
||||
---
|
||||
# Admin panel token (/admin). Generated once; read it with:
|
||||
# kubectl -n vaultwarden get secret admin-token -o jsonpath='{.data.ADMIN_TOKEN}' | base64 -d
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: admin-token
|
||||
spec:
|
||||
length: 48
|
||||
digits: 10
|
||||
symbols: 0
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: admin-token
|
||||
spec:
|
||||
refreshPolicy: CreatedOnce
|
||||
target:
|
||||
name: admin-token
|
||||
template:
|
||||
data:
|
||||
ADMIN_TOKEN: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: admin-token
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vaultwarden
|
||||
spec:
|
||||
selector:
|
||||
app: vaultwarden
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: http
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: airtrail
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: apps
|
||||
destination:
|
||||
namespace: airtrail
|
||||
server: https://kubernetes.default.svc
|
||||
source:
|
||||
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
||||
targetRevision: HEAD
|
||||
path: k8s/apps/airtrail
|
||||
syncPolicy:
|
||||
automated:
|
||||
selfHeal: true
|
||||
prune: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,66 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: airtrail
|
||||
labels:
|
||||
app: airtrail
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: airtrail
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: airtrail
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
containers:
|
||||
- name: airtrail
|
||||
image: johly/airtrail:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
env:
|
||||
- name: ORIGIN
|
||||
value: https://at.hexor.cy
|
||||
- name: DB_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: airtrail-creds
|
||||
key: DB_URL
|
||||
- name: UPLOAD_LOCATION
|
||||
value: /app/uploads
|
||||
- name: BODY_SIZE_LIMIT
|
||||
value: 20M
|
||||
volumeMounts:
|
||||
- name: uploads
|
||||
mountPath: /app/uploads
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
startupProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
periodSeconds: 30
|
||||
volumes:
|
||||
- name: uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: airtrail-uploads
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: airtrail-creds
|
||||
spec:
|
||||
target:
|
||||
name: airtrail-creds
|
||||
deletionPolicy: Delete
|
||||
template:
|
||||
type: Opaque
|
||||
data:
|
||||
DB_URL: |-
|
||||
postgresql://airtrail:{{ .airtrail | trim | urlquery | replace "+" "%20" }}@psql.psql.svc:5432/airtrail
|
||||
data:
|
||||
- secretKey: airtrail
|
||||
sourceRef:
|
||||
storeRef:
|
||||
name: vaultwarden-login
|
||||
kind: ClusterSecretStore
|
||||
remoteRef:
|
||||
conversionStrategy: Default
|
||||
decodingStrategy: None
|
||||
metadataPolicy: None
|
||||
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||
property: fields[21].value
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: airtrail-tls-ingress
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
|
||||
acme.cert-manager.io/http01-edit-in-place: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: at.hexor.cy
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: airtrail
|
||||
port:
|
||||
number: 3000
|
||||
tls:
|
||||
- secretName: airtrail-tls
|
||||
hosts:
|
||||
- at.hexor.cy
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- app.yaml
|
||||
- external-secrets.yaml
|
||||
- storage.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: airtrail
|
||||
labels:
|
||||
app: airtrail
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: airtrail
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 3000
|
||||
targetPort: http
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: airtrail-uploads
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -8,7 +8,7 @@ data:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PORT="${1:-5847}"
|
||||
PORT="${1:-5848}"
|
||||
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||
POLICY_FILE="${3:-/run/amnezia/policy/policy.conf}"
|
||||
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||
@@ -96,12 +96,18 @@ data:
|
||||
[[ "${source}" == \#* ]] && continue
|
||||
if [ -n "${extra:-}" ] \
|
||||
|| ! valid_ipv4_32 "${source}" \
|
||||
|| ! valid_ipv4_32 "${destination:-}" \
|
||||
|| ! routed_through_awg0 "${source}" \
|
||||
|| ! routed_through_awg0 "${destination}"; then
|
||||
echo "Invalid or out-of-tunnel policy line: ${source:-} ${destination:-} ${extra:-}" >&2
|
||||
"${IPTABLES[@]}" -F "${inactive}"
|
||||
return 1
|
||||
|| ! valid_ipv4_32 "${destination:-}"; then
|
||||
echo "Skipping malformed policy line: ${source:-} ${destination:-} ${extra:-}" >&2
|
||||
continue
|
||||
fi
|
||||
# A pair whose endpoints are not (yet) routed through awg0 is skipped
|
||||
# rather than fatal. During interface bring-up the awg0 route may not be
|
||||
# in place, and a missing ACCEPT simply leaves the pair subject to the
|
||||
# client-isolation DROP below (fail-closed) instead of aborting the whole
|
||||
# policy and crash-looping the container.
|
||||
if ! routed_through_awg0 "${source}" || ! routed_through_awg0 "${destination}"; then
|
||||
echo "Skipping out-of-tunnel policy pair: ${source} ${destination}" >&2
|
||||
continue
|
||||
fi
|
||||
"${IPTABLES[@]}" -A "${inactive}" \
|
||||
-i awg0 -o awg0 -s "${source}" -d "${destination}" \
|
||||
@@ -181,7 +187,7 @@ data:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PORT="${1:-5847}"
|
||||
PORT="${1:-5848}"
|
||||
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
|
||||
@@ -239,7 +245,7 @@ data:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PORT="${1:-5847}"
|
||||
PORT="${1:-5848}"
|
||||
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||
IPTABLES=(iptables -w 5)
|
||||
@@ -270,9 +276,13 @@ data:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FIRST_FORWARD_RULE="$("${IPTABLES[@]}" -S FORWARD | grep '^-A FORWARD ' | sed -n '1p')"
|
||||
[[ "${FIRST_FORWARD_RULE}" == *"--comment amneziawg-forward-jump"* ]]
|
||||
[[ "${FIRST_FORWARD_RULE}" == *"-j ${FORWARD_CHAIN}"* ]]
|
||||
# The amneziawg FORWARD jump must be present. firewall-up inserts it at
|
||||
# position 1, but CNI controllers (e.g. kube-router) legitimately re-assert
|
||||
# their own jump ahead of it. Requiring the strict first position makes the
|
||||
# readiness probe flap as the two fight over rule order. Isolation still holds
|
||||
# as long as the jump is traversed and no earlier rule accepts awg0-to-awg0
|
||||
# traffic (kube-router's chain returns non-pod traffic without a decision).
|
||||
"${IPTABLES[@]}" -C FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
|
||||
ACTIVE_POLICY="$("${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null \
|
||||
| awk '$1 == "-A" && $3 == "-j" && $4 ~ /^AMNEZIAWG-POLICY-[AB]$/ { print $4; exit }')"
|
||||
[ -n "${ACTIVE_POLICY}" ]
|
||||
@@ -362,12 +372,12 @@ data:
|
||||
apply_live_config() {
|
||||
# Fail closed during a peer/key and policy transition. /dev/null produces
|
||||
# an Internet-only policy with all awg0-to-awg0 traffic isolated.
|
||||
/scripts/firewall-up.sh 5847 10.8.0.0/16 /dev/null
|
||||
/scripts/firewall-up.sh 5848 10.8.0.0/16 /dev/null
|
||||
render_config
|
||||
awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}"
|
||||
chmod 0600 "${SYNC_CONFIG}"
|
||||
awg syncconf awg0 "${SYNC_CONFIG}"
|
||||
/scripts/firewall-up.sh 5847 10.8.0.0/16 "${POLICY_FILE}"
|
||||
/scripts/firewall-up.sh 5848 10.8.0.0/16 "${POLICY_FILE}"
|
||||
}
|
||||
|
||||
watch_client_config() {
|
||||
|
||||
@@ -52,7 +52,7 @@ spec:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
- name: PORT
|
||||
value: "5847"
|
||||
value: "5848"
|
||||
command:
|
||||
- /bin/bash
|
||||
- -lc
|
||||
@@ -116,14 +116,14 @@ spec:
|
||||
- /scripts/run.sh
|
||||
ports:
|
||||
- name: awg
|
||||
containerPort: 5847
|
||||
containerPort: 5848
|
||||
protocol: UDP
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/bash
|
||||
- -lc
|
||||
- /scripts/firewall-check.sh 5847 10.8.0.0/16
|
||||
- /scripts/firewall-check.sh 5848 10.8.0.0/16
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 8
|
||||
@@ -133,7 +133,7 @@ spec:
|
||||
command:
|
||||
- /bin/bash
|
||||
- -lc
|
||||
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
|
||||
- /scripts/firewall-check.sh 5848 10.8.0.0/16 || /scripts/firewall-up.sh 5848 10.8.0.0/16
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 15
|
||||
failureThreshold: 12
|
||||
@@ -142,7 +142,7 @@ spec:
|
||||
command:
|
||||
- /bin/bash
|
||||
- -lc
|
||||
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
|
||||
- /scripts/firewall-check.sh 5848 10.8.0.0/16 || /scripts/firewall-up.sh 5848 10.8.0.0/16
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 15
|
||||
|
||||
@@ -16,21 +16,21 @@ spec:
|
||||
[Interface]
|
||||
PrivateKey = {{ .server_private_key }}
|
||||
Address = 10.8.0.1/16
|
||||
ListenPort = 5847
|
||||
ListenPort = 5848
|
||||
MTU = 1376
|
||||
Jc = 4
|
||||
Jmin = 64
|
||||
Jmax = 128
|
||||
S1 = 15
|
||||
S2 = 18
|
||||
S3 = 20
|
||||
S4 = 23
|
||||
H1 = 1020325451
|
||||
H2 = 3288052141
|
||||
H3 = 1766607858
|
||||
H4 = 2528465083
|
||||
PostUp = /scripts/firewall-up.sh 5847 10.8.0.0/16
|
||||
PostDown = /scripts/firewall-down.sh 5847 10.8.0.0/16
|
||||
Jc = 5
|
||||
Jmin = 96
|
||||
Jmax = 320
|
||||
S1 = 37
|
||||
S2 = 47
|
||||
S3 = 29
|
||||
S4 = 43
|
||||
H1 = 1247270921
|
||||
H2 = 1956534873
|
||||
H3 = 886504771
|
||||
H4 = 1623791047
|
||||
PostUp = /scripts/firewall-up.sh 5848 10.8.0.0/16
|
||||
PostDown = /scripts/firewall-down.sh 5848 10.8.0.0/16
|
||||
data:
|
||||
- secretKey: server_private_key
|
||||
sourceRef:
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
kubernetes.io/hostname: cy.tail2fe2d.ts.net
|
||||
containers:
|
||||
- name: amnezia-fellow
|
||||
image: ultradesu/amnezia-fellow:latest
|
||||
image: ultradesu/amnezia-fellow:1.0.2
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -8,3 +8,4 @@ resources:
|
||||
- ingress.yaml
|
||||
- deployment.yaml
|
||||
- servicemonitor.yaml
|
||||
- socks-proxy.yaml
|
||||
@@ -0,0 +1,106 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: furumi-socks-proxy
|
||||
labels:
|
||||
app: furumi-socks-proxy
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: furumi-socks-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: furumi-socks-proxy
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||
tolerations:
|
||||
- key: workload
|
||||
operator: Equal
|
||||
value: ai
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: socks-proxy
|
||||
image: serjs/go-socks5-proxy:v0.0.4
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: REQUIRE_AUTH
|
||||
value: "true"
|
||||
- name: PROXY_USER
|
||||
value: furumi
|
||||
- name: PROXY_PASSWORD
|
||||
value: furumi
|
||||
- name: PROXY_PORT
|
||||
value: "1080"
|
||||
ports:
|
||||
- name: socks5
|
||||
containerPort: 1080
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: socks5
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: socks5
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: furumi-socks-proxy
|
||||
labels:
|
||||
app: furumi-socks-proxy
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: furumi-socks-proxy
|
||||
ports:
|
||||
- name: socks5
|
||||
protocol: TCP
|
||||
port: 1080
|
||||
targetPort: socks5
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: furumi-socks-proxy-ingress
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: furumi-socks-proxy
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: furumi-player
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 1080
|
||||
@@ -48,7 +48,7 @@ spec:
|
||||
value: "true"
|
||||
- name: GITEA__service__CAPTCHA_TYPE
|
||||
value: "cfturnstile"
|
||||
- name: GITEA__webhook__ALLOWED_HOST_LIST
|
||||
- name: GITEA__security__ALLOWED_HOST_LIST
|
||||
value: "*"
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
@@ -34,7 +34,7 @@ spec:
|
||||
mountPath: /templates/subscription
|
||||
containers:
|
||||
- name: pasarguard-web
|
||||
image: pasarguard/panel:v5.0.3
|
||||
image: pasarguard/panel:v5.3.0
|
||||
imagePullPolicy: Always
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
@@ -10,7 +10,7 @@ resources:
|
||||
helmCharts:
|
||||
- name: argo-cd
|
||||
repo: https://argoproj.github.io/argo-helm
|
||||
version: 10.2.1
|
||||
version: 10.6.0
|
||||
releaseName: argocd
|
||||
namespace: argocd
|
||||
valuesFile: values.yaml
|
||||
|
||||
@@ -10,7 +10,7 @@ resources:
|
||||
helmCharts:
|
||||
- name: cert-manager
|
||||
repo: https://charts.jetstack.io
|
||||
version: 1.20.0
|
||||
version: 1.21.1
|
||||
releaseName: cert-manager
|
||||
namespace: cert-manager
|
||||
valuesFile: values.yaml
|
||||
|
||||
@@ -7,7 +7,7 @@ resources:
|
||||
helmCharts:
|
||||
- name: external-secrets
|
||||
repo: https://charts.external-secrets.io
|
||||
version: 1.1.0
|
||||
version: 2.10.0
|
||||
releaseName: external-secrets
|
||||
namespace: external-secrets
|
||||
valuesFile: values.yaml
|
||||
|
||||
@@ -9,7 +9,7 @@ resources:
|
||||
helmCharts:
|
||||
- name: keycloakx
|
||||
repo: https://codecentric.github.io/helm-charts
|
||||
version: 7.1.11
|
||||
version: 7.3.1
|
||||
releaseName: keycloak
|
||||
namespace: keycloak
|
||||
valuesFile: values.yaml
|
||||
@@ -7,7 +7,7 @@ kind: Kustomization
|
||||
helmCharts:
|
||||
- name: longhorn
|
||||
repo: https://charts.longhorn.io
|
||||
version: 1.12.0
|
||||
version: 1.12.1
|
||||
releaseName: longhorn
|
||||
namespace: longhorn-system
|
||||
valuesFile: values.yaml
|
||||
|
||||
@@ -144,6 +144,8 @@ spec:
|
||||
{{ .amnezia_fellow }}
|
||||
USER_web_petting: |-
|
||||
{{ .web_petting }}
|
||||
USER_airtrail: |-
|
||||
{{ .airtrail }}
|
||||
data:
|
||||
- secretKey: authentik
|
||||
sourceRef:
|
||||
@@ -365,3 +367,14 @@ spec:
|
||||
metadataPolicy: None
|
||||
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||
property: fields[20].value
|
||||
- secretKey: airtrail
|
||||
sourceRef:
|
||||
storeRef:
|
||||
name: vaultwarden-login
|
||||
kind: ClusterSecretStore
|
||||
remoteRef:
|
||||
conversionStrategy: Default
|
||||
decodingStrategy: None
|
||||
metadataPolicy: None
|
||||
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||
property: fields[21].value
|
||||
@@ -14,15 +14,15 @@ resources:
|
||||
helmCharts:
|
||||
- name: kube-prometheus-stack
|
||||
repo: https://prometheus-community.github.io/helm-charts
|
||||
version: 84.3.0
|
||||
version: 88.6.2
|
||||
releaseName: prometheus
|
||||
namespace: prometheus
|
||||
valuesFile: prom-values.yaml
|
||||
includeCRDs: true
|
||||
|
||||
- name: loki
|
||||
repo: https://grafana.github.io/helm-charts
|
||||
version: 6.29.0
|
||||
repo: https://grafana-community.github.io/helm-charts
|
||||
version: 18.11.7
|
||||
releaseName: loki
|
||||
namespace: prometheus
|
||||
valuesFile: loki-values.yaml
|
||||
@@ -30,7 +30,7 @@ helmCharts:
|
||||
|
||||
- name: promtail
|
||||
repo: https://grafana.github.io/helm-charts
|
||||
version: 6.16.6
|
||||
version: 6.17.1
|
||||
releaseName: promtail
|
||||
namespace: prometheus
|
||||
valuesFile: promtail-values.yaml
|
||||
|
||||
@@ -95,6 +95,11 @@ prometheus:
|
||||
|
||||
grafana:
|
||||
enabled: true
|
||||
|
||||
sidecar:
|
||||
datasources:
|
||||
alertmanager:
|
||||
uid: ff9iga3xqregwc
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
serviceAccountName: system-upgrade
|
||||
upgrade:
|
||||
image: rancher/k3s-upgrade
|
||||
version: v1.36.1+k3s1
|
||||
version: v1.36.4+k3s1
|
||||
---
|
||||
# Agent plan
|
||||
apiVersion: upgrade.cattle.io/v1
|
||||
@@ -43,4 +43,4 @@ spec:
|
||||
serviceAccountName: system-upgrade
|
||||
upgrade:
|
||||
image: rancher/k3s-upgrade
|
||||
version: v1.36.1+k3s1
|
||||
version: v1.36.4+k3s1
|
||||
Generated
+2
@@ -7,6 +7,7 @@ provider "registry.terraform.io/hashicorp/kubernetes" {
|
||||
hashes = [
|
||||
"h1:G9QqKNpcztBRqrywtlNylFJSpGzDfRFtO8hcWLdkvRY=",
|
||||
"h1:iDIPyL9K9FXc8ievR5vRmXsgT1/I9Vq9CFDKm1Zo8bQ=",
|
||||
"h1:oodIAuFMikXNmEtil5MQgP4dfSctUBYQiGJfjbsF3NY=",
|
||||
"zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0",
|
||||
"zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20",
|
||||
"zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c",
|
||||
@@ -27,6 +28,7 @@ provider "registry.terraform.io/keycloak/keycloak" {
|
||||
constraints = ">= 5.0.0"
|
||||
hashes = [
|
||||
"h1:3DuKdVeOxwULh7l6bvJKWZvsgSZo92rtnrdvyp+X2Lc=",
|
||||
"h1:VSlrHkPQm4mczl+5ttuj6jAh2eVvupaoeymEn9Qi5vo=",
|
||||
"h1:reFEBcuBZf2SVqDa1YIaNLuIW72uEVsOu5hHhQgfHi0=",
|
||||
"zh:19be4505b17e4818db121a82917cb6723019cf379cfb82b720eaa2886f15bede",
|
||||
"zh:2bd1565ed22db6a9fb50d60626e22c277f3b034a71f65e6c0011e42f56cad2bb",
|
||||
|
||||
@@ -243,6 +243,7 @@ resource "keycloak_openid_client" "oauth2_app" {
|
||||
valid_redirect_uris = each.value.redirect_uris
|
||||
valid_post_logout_redirect_uris = each.value.post_logout_redirect_uris
|
||||
web_origins = each.value.web_origins
|
||||
access_token_lifespan = each.value.access_token_lifespan
|
||||
}
|
||||
|
||||
resource "keycloak_openid_group_membership_protocol_mapper" "oauth2_app_groups" {
|
||||
@@ -292,7 +293,7 @@ resource "keycloak_group" "oauth2_app" {
|
||||
resource "kubernetes_config_map_v1" "auth_proxy_routes" {
|
||||
metadata {
|
||||
name = "auth-proxy-routes"
|
||||
namespace = "auth-proxy"
|
||||
namespace = "keycloak"
|
||||
}
|
||||
|
||||
data = {
|
||||
|
||||
@@ -19,5 +19,6 @@ provider "keycloak" {
|
||||
}
|
||||
|
||||
provider "kubernetes" {
|
||||
config_path = "~/.kube/config"
|
||||
config_path = "~/.kube/config"
|
||||
config_context = "hexor-lab"
|
||||
}
|
||||
@@ -37,11 +37,19 @@ oauth2_applications = {
|
||||
post_logout_redirect_uris = ["https://gt.hexor.cy/*"]
|
||||
}
|
||||
ArgoCD = {
|
||||
redirect_uris = ["https://ag.hexor.cy/api/dex/callback"]
|
||||
web_origins = ["https://ag.hexor.cy"]
|
||||
post_logout_redirect_uris = ["https://ag.hexor.cy/*"]
|
||||
redirect_uris = ["https://ag.hexor.cy/api/dex/callback", "https://ag.btwiusearch.net/api/dex/callback"]
|
||||
web_origins = ["https://ag.hexor.cy", "https://ag.btwiusearch.net"]
|
||||
post_logout_redirect_uris = ["https://ag.hexor.cy/*", "https://ag.btwiusearch.net/*"]
|
||||
extra_optional_scopes = ["offline_access"]
|
||||
}
|
||||
Vaultwarden = {
|
||||
redirect_uris = ["https://vw.btwiusearch.net/identity/connect/oidc-signin"]
|
||||
web_origins = ["https://vw.btwiusearch.net"]
|
||||
post_logout_redirect_uris = ["https://vw.btwiusearch.net/*"]
|
||||
extra_optional_scopes = ["offline_access"]
|
||||
# Bitwarden clients treat tokens shorter than ~10 minutes as expired.
|
||||
access_token_lifespan = "900"
|
||||
}
|
||||
PGAdmin = {
|
||||
redirect_uris = ["https://pg.hexor.cy/oauth2/authorize"]
|
||||
web_origins = ["https://pg.hexor.cy"]
|
||||
|
||||
@@ -51,6 +51,7 @@ variable "oauth2_applications" {
|
||||
extra_default_scopes = optional(list(string), [])
|
||||
extra_optional_scopes = optional(list(string), [])
|
||||
allowed_groups = optional(list(string), [])
|
||||
access_token_lifespan = optional(string) # seconds; null keeps the realm default
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
Reference in new issue
Block a user